This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-website.git


The following commit(s) were added to refs/heads/main by this push:
     new 065bea5e Camel CLI installers and documentation (#1699)
065bea5e is described below

commit 065bea5ebe5f2801b404853fd107b1c4a22f5e53
Author: Adriano Machado <[email protected]>
AuthorDate: Mon Jul 20 16:33:04 2026 -0400

    Camel CLI installers and documentation (#1699)
    
    * feat: publish Camel CLI Launcher manifests for 4.13.0 through 4.21.0
    
    Per-version SHA-256 manifests (format=1, version, tar_sha256, zip_sha256)
    for every camel-launcher release with -bin archives on Maven Central,
    plus latest.properties pointing at 4.21.0. The 4.21.0 manifest matches
    the test fixture in apache/camel's WebsiteInstallerFixture; the rest are
    computed directly from the published -bin.tar.gz/-bin.zip archives.
    
    Co-Authored-By: Claude Sonnet 5 <[email protected]>
    
    * feat: add Camel CLI Launcher manifest publishing helper
    
    * feat: publish Camel CLI Launcher installers
    
    install.sh / install.ps1 fetched byte-identical from apache/camel main
    (dsl/camel-jbang/camel-launcher/src/install). Do not edit here; regenerate
    with ./update-camel-cli-manifest.sh. Manifests for 4.13.0-4.21.0 were
    published in a prior commit.
    
    * docs: add Camel CLI Launcher installation guide
    
    * blog: announce the Camel CLI Launcher installers
    
    * blog: revise Camel CLI Launcher post for voice and a route example
    
    - Link to the installers directory in apache/camel instead of the PR
    - Match the voice of recent posts (direct, concrete, no filler)
    - Lead with how much friction the installer removes vs. jbang setup
    - Add a real camel init/camel run example with verified output
    
    * blog: add featured image to Camel CLI Launcher installers post
    
    ---------
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
---
 .../07/camel-cli-launcher-installers/featured.png  | Bin 0 -> 2173506 bytes
 .../2026/07/camel-cli-launcher-installers/index.md |  88 +++++++
 content/download/_index.md                         |   6 +
 content/download/camel-cli-launcher.md             |  67 +++++
 static/camel-cli/releases/4.13.0.properties        |   4 +
 static/camel-cli/releases/4.14.0.properties        |   4 +
 static/camel-cli/releases/4.14.1.properties        |   4 +
 static/camel-cli/releases/4.14.2.properties        |   4 +
 static/camel-cli/releases/4.14.3.properties        |   4 +
 static/camel-cli/releases/4.14.4.properties        |   4 +
 static/camel-cli/releases/4.14.5.properties        |   4 +
 static/camel-cli/releases/4.14.6.properties        |   4 +
 static/camel-cli/releases/4.14.7.properties        |   4 +
 static/camel-cli/releases/4.14.8.properties        |   4 +
 static/camel-cli/releases/4.15.0.properties        |   4 +
 static/camel-cli/releases/4.16.0.properties        |   4 +
 static/camel-cli/releases/4.17.0.properties        |   4 +
 static/camel-cli/releases/4.18.0.properties        |   4 +
 static/camel-cli/releases/4.18.1.properties        |   4 +
 static/camel-cli/releases/4.18.2.properties        |   4 +
 static/camel-cli/releases/4.18.3.properties        |   4 +
 static/camel-cli/releases/4.19.0.properties        |   4 +
 static/camel-cli/releases/4.20.0.properties        |   4 +
 static/camel-cli/releases/4.21.0.properties        |   4 +
 static/camel-cli/releases/latest.properties        |   4 +
 static/install.ps1                                 | 292 +++++++++++++++++++++
 static/install.sh                                  | 290 ++++++++++++++++++++
 update-camel-cli-manifest.sh                       | 131 +++++++++
 28 files changed, 958 insertions(+)

diff --git a/content/blog/2026/07/camel-cli-launcher-installers/featured.png 
b/content/blog/2026/07/camel-cli-launcher-installers/featured.png
new file mode 100644
index 00000000..0c007485
Binary files /dev/null and 
b/content/blog/2026/07/camel-cli-launcher-installers/featured.png differ
diff --git a/content/blog/2026/07/camel-cli-launcher-installers/index.md 
b/content/blog/2026/07/camel-cli-launcher-installers/index.md
new file mode 100644
index 00000000..29a07775
--- /dev/null
+++ b/content/blog/2026/07/camel-cli-launcher-installers/index.md
@@ -0,0 +1,88 @@
+---
+title: "Installing the Apache Camel CLI Launcher"
+date: 2026-07-20
+draft: false
+authors: [ ammachado ]
+categories: [ "Tooling" ]
+keywords: ["apache camel", "camel cli", "installer", "getting started", 
"developer experience"]
+preview: "Install the Camel CLI Launcher with a single, checksum-verified 
command on macOS, Linux, and Windows, no JDK setup or separate jbang install 
required."
+---
+
+Getting started with the Camel CLI used to mean installing a JDK, then jbang, 
then
+finally running `jbang camel@apache/camel init` for the first time. That 
friction
+is gone. `install.sh` and `install.ps1` get the Camel CLI Launcher running on 
your
+machine with a single command, no package manager and no separate jbang install
+required.
+
+## One-line install
+
+On macOS and Linux:
+
+```bash
+curl -fsSL https://camel.apache.org/install.sh | sh
+```
+
+On Windows, in PowerShell:
+
+```powershell
+irm https://camel.apache.org/install.ps1 | iex
+```
+
+Both installers resolve and install the latest published release by default, or
+a specific version if you request one with `--version` (`install.sh`) or 
`-Version`
+(`install.ps1`). Installation is always per-user and never requires `sudo` or
+elevation.
+
+## From zero to a running route
+
+Once the installer finishes, `camel` is on your PATH. Create a route:
+
+```bash
+camel init hello.yaml
+```
+
+This generates a minimal route that fires a timer and logs a message:
+
+```yaml
+- route:
+    from:
+      uri: timer:yaml
+      parameters:
+        period: "1000"
+      steps:
+        - setBody:
+            simple: Hello Camel from ${routeId}
+        - log: ${body}
+```
+
+Run it:
+
+```bash
+camel run hello.yaml
+```
+
+```
+...     Started route1 (timer://yaml)
+... hello.yaml:9 : Hello Camel from route1
+... hello.yaml:9 : Hello Camel from route1
+```
+
+That is the entire path from a fresh machine to a running integration: one 
install
+command, `camel init`, `camel run`.
+
+## Verify before execute
+
+The installers are built around a simple security property: the launcher 
archive
+is downloaded from [Maven 
Central](https://repo1.maven.org/maven2/org/apache/camel/camel-launcher/),
+but it is verified against a SHA-256 checksum published on `camel.apache.org`
+before anything is extracted or run. The archive and the checksum come from two
+independent hosts, so tampering would require compromising both at the same 
time.
+The installer also rejects unsafe archive contents and confirms a Java 17+
+runtime before activating the new version.
+
+## Learn more
+
+See the [installation guide](/download/camel-cli-launcher/) for pinned versions
+and the inspect-before-run workflow. The installer scripts themselves live in 
the
+Apache Camel repository, under
+[dsl/camel-jbang/camel-launcher/src/install](https://github.com/apache/camel/tree/main/dsl/camel-jbang/camel-launcher/src/install).
diff --git a/content/download/_index.md b/content/download/_index.md
index 2714794b..d39b16bd 100644
--- a/content/download/_index.md
+++ b/content/download/_index.md
@@ -23,6 +23,12 @@ download above. These machine-readable inventories list 
every dependency in the
 tools like [OWASP Dependency-Track](https://dependencytrack.org/) for 
automated vulnerability scanning.
 To generate an SBOM for your own Camel application, see the [Generating 
SBOMs](/manual/sbom.html) guide.
 
+## Camel CLI Launcher
+
+You can install the [Camel CLI Launcher](/download/camel-cli-launcher/) on a
+single machine with a one-line command, without a package manager. See the
+[installation guide](/download/camel-cli-launcher/) for details.
+
 ## Keys
 
 You can verify your download by following these 
[procedures](http://www.apache.org/info/verification.html) and using these 
[KEYS](https://www.apache.org/dist/camel/KEYS).
diff --git a/content/download/camel-cli-launcher.md 
b/content/download/camel-cli-launcher.md
new file mode 100644
index 00000000..1f146d2d
--- /dev/null
+++ b/content/download/camel-cli-launcher.md
@@ -0,0 +1,67 @@
+---
+title: "Install the Camel CLI Launcher"
+---
+
+The Camel CLI Launcher can be installed on a single machine without a package
+manager. The installer scripts published on this site download the launcher
+from Maven Central and verify it against a SHA-256 checksum published here
+before running it. Installation is always per-user and never requires `sudo`
+or elevation.
+
+## Requirements
+
+- A Java 17 or newer runtime discoverable on your system.
+- `curl` (macOS and Linux) or PowerShell 5.1+ (Windows).
+
+## Quick install
+
+With no arguments the installer resolves and installs the latest published
+release.
+
+### macOS and Linux
+
+```bash
+curl -fsSL https://camel.apache.org/install.sh | sh
+```
+
+### Windows (PowerShell)
+
+```powershell
+irm https://camel.apache.org/install.ps1 | iex
+```
+
+## Install a specific version, or inspect before running
+
+If you prefer not to pipe a script straight into a shell, download it, read it,
+then run it. The same approach lets you pin an exact `X.Y.Z` version.
+
+### macOS and Linux
+
+```bash
+curl -fsSL https://camel.apache.org/install.sh -o install.sh
+less install.sh
+sh install.sh --version 4.21.0
+```
+
+### Windows (PowerShell)
+
+```powershell
+irm https://camel.apache.org/install.ps1 -OutFile install.ps1
+notepad install.ps1
+./install.ps1 -Version 4.21.0
+```
+
+## How the security works
+
+The installer downloads the release archive from
+[Maven 
Central](https://repo1.maven.org/maven2/org/apache/camel/camel-launcher/),
+but verifies it against a SHA-256 checksum recorded in a manifest served from
+`camel.apache.org`. The archive and the checksum come from two independent
+hosts, so tampering would require compromising both at once. The installer also
+rejects archives containing absolute paths, `../` traversal, escaping symlinks,
+or more than one top-level directory, and runs the staged launcher once to
+confirm a Java 17+ runtime before activating it. If that check fails, any
+previously installed version is left untouched.
+
+The installers are maintained in the Apache Camel repository
+([apache/camel#24682](https://github.com/apache/camel/pull/24682)).
diff --git a/static/camel-cli/releases/4.13.0.properties 
b/static/camel-cli/releases/4.13.0.properties
new file mode 100644
index 00000000..0f9a940b
--- /dev/null
+++ b/static/camel-cli/releases/4.13.0.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.13.0
+tar_sha256=3d1a31b2ecb6d345fa491574e400c6099d68b5a7349b402c7ee4c01cc840645b
+zip_sha256=80c45a45e500998c21a6b111b9ddf80ea2543913b7955f1d92a3ec16affbdd72
diff --git a/static/camel-cli/releases/4.14.0.properties 
b/static/camel-cli/releases/4.14.0.properties
new file mode 100644
index 00000000..077e9654
--- /dev/null
+++ b/static/camel-cli/releases/4.14.0.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.14.0
+tar_sha256=23efaa431f0faa1ee3183e333b83601952277ab9f881d448c52564abe7447ec0
+zip_sha256=9d428f765692c6d115a65b2c8199640593736ef0a7fd1a1f63bfdae69401a3de
diff --git a/static/camel-cli/releases/4.14.1.properties 
b/static/camel-cli/releases/4.14.1.properties
new file mode 100644
index 00000000..28c78b56
--- /dev/null
+++ b/static/camel-cli/releases/4.14.1.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.14.1
+tar_sha256=4a1157f2a6a40c992335a246c759c01706be209852a652cfd5633e37d61b8442
+zip_sha256=787881f4c9d7209137f067a79c0d2a0ea373e82e1e4f12c60ed744c1a32f0f72
diff --git a/static/camel-cli/releases/4.14.2.properties 
b/static/camel-cli/releases/4.14.2.properties
new file mode 100644
index 00000000..0854fbea
--- /dev/null
+++ b/static/camel-cli/releases/4.14.2.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.14.2
+tar_sha256=4c59276f1ffc965ef33c1f591abb5a6837db89611902d18ba2cd81747b620bce
+zip_sha256=c4af337b0e7edc18f3bc4d9487b0b897bab6ab3297d0881d26601654255a8f00
diff --git a/static/camel-cli/releases/4.14.3.properties 
b/static/camel-cli/releases/4.14.3.properties
new file mode 100644
index 00000000..4e02380e
--- /dev/null
+++ b/static/camel-cli/releases/4.14.3.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.14.3
+tar_sha256=bf07215a8a5612f13435cc002aee3b2833ba6d221eebe9202e5a649d95e5a5b7
+zip_sha256=d9d7683df2c3442055d7f454022ce08d0ccd1dc07935e10d6c4dea4e7b192435
diff --git a/static/camel-cli/releases/4.14.4.properties 
b/static/camel-cli/releases/4.14.4.properties
new file mode 100644
index 00000000..9cab236f
--- /dev/null
+++ b/static/camel-cli/releases/4.14.4.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.14.4
+tar_sha256=cf23867122e719d2e41dbf1975807f7a4b5e126c1e4e0adfd1f8b234a18b2cee
+zip_sha256=892163aa6002ff5f94b0a2ca1208d0e527e4a7f52f6b54855983c669b1fc6f56
diff --git a/static/camel-cli/releases/4.14.5.properties 
b/static/camel-cli/releases/4.14.5.properties
new file mode 100644
index 00000000..2776611b
--- /dev/null
+++ b/static/camel-cli/releases/4.14.5.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.14.5
+tar_sha256=77d4eda924c97687b6c8d09d34e5c15187ed214c5284edc8086c612d73c32896
+zip_sha256=1058f42ef770adc1b05c770ecadab02ebd08eb0334381d679fecb1ad10a74e56
diff --git a/static/camel-cli/releases/4.14.6.properties 
b/static/camel-cli/releases/4.14.6.properties
new file mode 100644
index 00000000..d95ad3d9
--- /dev/null
+++ b/static/camel-cli/releases/4.14.6.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.14.6
+tar_sha256=16826411fc4553a92d1df1c633be942f5ec315911ec48e062ca2f65cd228a68a
+zip_sha256=ece1939eedaad3b5d999deb21101ea50cbe35631d756ec8aefb01e0a64972522
diff --git a/static/camel-cli/releases/4.14.7.properties 
b/static/camel-cli/releases/4.14.7.properties
new file mode 100644
index 00000000..61fe8184
--- /dev/null
+++ b/static/camel-cli/releases/4.14.7.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.14.7
+tar_sha256=d74214be90d330721292a50d1922c553f438348ab5e05832f84c6d4857dd1f94
+zip_sha256=b28541f594c1bf6e168240acaae9f9e1ee4fe2e83e63fc6b4d76d19a3747f09d
diff --git a/static/camel-cli/releases/4.14.8.properties 
b/static/camel-cli/releases/4.14.8.properties
new file mode 100644
index 00000000..ac58787d
--- /dev/null
+++ b/static/camel-cli/releases/4.14.8.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.14.8
+tar_sha256=21b6e2ea881cf06c99dbf6502cbd3dc98f59e44f8491b8d22c4758b3942fa37f
+zip_sha256=4b09c9da80f6fb5f87323f626422844a1847731f49b5c62c7681221924798b61
diff --git a/static/camel-cli/releases/4.15.0.properties 
b/static/camel-cli/releases/4.15.0.properties
new file mode 100644
index 00000000..cf55369c
--- /dev/null
+++ b/static/camel-cli/releases/4.15.0.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.15.0
+tar_sha256=bc7d40e4ce18b8a195e1e6333e8ec147efe697a2500dd15840c6b9ca6abbf14a
+zip_sha256=38506074a7bb4f39cd48bbc3f9c3a25a2a93e9c0ed62962cd03e4fd53d1ac1aa
diff --git a/static/camel-cli/releases/4.16.0.properties 
b/static/camel-cli/releases/4.16.0.properties
new file mode 100644
index 00000000..576c1d6b
--- /dev/null
+++ b/static/camel-cli/releases/4.16.0.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.16.0
+tar_sha256=19e784eee53213b18cb620e3b3888dcc1fc4d925c41b48846db7825d38b28653
+zip_sha256=1ea3368b629f60443b907e28a9f8eb7d57bc0ba5036e4cfbfd30f1a9fed79602
diff --git a/static/camel-cli/releases/4.17.0.properties 
b/static/camel-cli/releases/4.17.0.properties
new file mode 100644
index 00000000..0955bdcf
--- /dev/null
+++ b/static/camel-cli/releases/4.17.0.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.17.0
+tar_sha256=6aae94b30a33a04c2daed938333956f6d41615fc0edd9a27219dbe9665595f72
+zip_sha256=9d6bdd6552020d6aac3dea83de32765e03e2aa5b9cf278598c1575d2416258c6
diff --git a/static/camel-cli/releases/4.18.0.properties 
b/static/camel-cli/releases/4.18.0.properties
new file mode 100644
index 00000000..2692894b
--- /dev/null
+++ b/static/camel-cli/releases/4.18.0.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.18.0
+tar_sha256=3194acbe30530a8814a58ad86042a43800435e459ab8e6fc17141d4a6d05d85f
+zip_sha256=a202622ef5800704e952dfb492bad3cc887f10c9f5043b40be6adbe15b389bdd
diff --git a/static/camel-cli/releases/4.18.1.properties 
b/static/camel-cli/releases/4.18.1.properties
new file mode 100644
index 00000000..857c7901
--- /dev/null
+++ b/static/camel-cli/releases/4.18.1.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.18.1
+tar_sha256=efe028eda43216d1150828aed2449c0af8c62f35446fd0c4b3835de2a02a8093
+zip_sha256=950fc433d3586be7893542d06fdb7175ed1b6999bcb77e895a0e0592670c8042
diff --git a/static/camel-cli/releases/4.18.2.properties 
b/static/camel-cli/releases/4.18.2.properties
new file mode 100644
index 00000000..d6674c04
--- /dev/null
+++ b/static/camel-cli/releases/4.18.2.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.18.2
+tar_sha256=5288962fee8cf22df2e3717350205fd1cdc47ba449436a43a677ba0cb84a49aa
+zip_sha256=513455358de4af716d3fcd524e506d40604ed417d86304292c59a23f87452845
diff --git a/static/camel-cli/releases/4.18.3.properties 
b/static/camel-cli/releases/4.18.3.properties
new file mode 100644
index 00000000..47d97000
--- /dev/null
+++ b/static/camel-cli/releases/4.18.3.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.18.3
+tar_sha256=8128d5c8259f72b29b25f857f97eb7f892067801fc2c2e2aa53ca1c9a780d01d
+zip_sha256=7d0cc0f74615497e1a640695f030e7747dc3a15780cb1ec5906ba4499849c627
diff --git a/static/camel-cli/releases/4.19.0.properties 
b/static/camel-cli/releases/4.19.0.properties
new file mode 100644
index 00000000..0e7edbcc
--- /dev/null
+++ b/static/camel-cli/releases/4.19.0.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.19.0
+tar_sha256=21b198c34b0028f3e0241e14f81af6e840a10e6ba0f2fb28f16055270a2c35d3
+zip_sha256=2eb1a146a9b3c6f9fd10a5493263296a428ad2ada11280df779c36524b510a2c
diff --git a/static/camel-cli/releases/4.20.0.properties 
b/static/camel-cli/releases/4.20.0.properties
new file mode 100644
index 00000000..3e4bdeda
--- /dev/null
+++ b/static/camel-cli/releases/4.20.0.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.20.0
+tar_sha256=5f2332580392f9c77b2489c728e3052848fe5b8c201ae701fe2bf37b83351782
+zip_sha256=086a7b16d0a9ffc951f2d5f4dcb8c09a15a08da73ced6e002d56279664667de6
diff --git a/static/camel-cli/releases/4.21.0.properties 
b/static/camel-cli/releases/4.21.0.properties
new file mode 100644
index 00000000..8ca2416e
--- /dev/null
+++ b/static/camel-cli/releases/4.21.0.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.21.0
+tar_sha256=24c247b9418292bce794b8ad2333691263c3d0de9a4a0fbc767942669b71984c
+zip_sha256=949d39606fc5d4f246a9ea5166f1f8597e75229332653f48b667da73fb87d80e
diff --git a/static/camel-cli/releases/latest.properties 
b/static/camel-cli/releases/latest.properties
new file mode 100644
index 00000000..8ca2416e
--- /dev/null
+++ b/static/camel-cli/releases/latest.properties
@@ -0,0 +1,4 @@
+format=1
+version=4.21.0
+tar_sha256=24c247b9418292bce794b8ad2333691263c3d0de9a4a0fbc767942669b71984c
+zip_sha256=949d39606fc5d4f246a9ea5166f1f8597e75229332653f48b667da73fb87d80e
diff --git a/static/install.ps1 b/static/install.ps1
new file mode 100644
index 00000000..c57fc6b3
--- /dev/null
+++ b/static/install.ps1
@@ -0,0 +1,292 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+param(
+    [string] $Version
+)
+
+$ErrorActionPreference = 'Stop'
+
+# Test seams only: production installs never set these, so the defaults below 
are always used.
+$ManifestBaseUrl = if ($env:CAMEL_INSTALL_MANIFEST_BASE_URL) { 
$env:CAMEL_INSTALL_MANIFEST_BASE_URL } else { 
'https://camel.apache.org/camel-cli/releases' }
+$MavenBaseUrl = if ($env:CAMEL_INSTALL_MAVEN_BASE_URL) { 
$env:CAMEL_INSTALL_MAVEN_BASE_URL } else { 
'https://repo1.maven.org/maven2/org/apache/camel/camel-launcher' }
+$CaCertPath = $env:CAMEL_INSTALL_CA_CERT
+
+$InstallRoot = Join-Path $env:LOCALAPPDATA 'Apache Camel'
+$DataRoot = Join-Path $InstallRoot 'cli\versions'
+$BinDir = Join-Path $InstallRoot 'bin'
+
+function Fail {
+    param([string] $Message)
+    [Console]::Error.WriteLine("install.ps1: $Message")
+    exit 1
+}
+
+function Test-ValidVersion {
+    param([string] $Value)
+    return $Value -match '\A[0-9]+\.[0-9]+\.[0-9]+\z'
+}
+
+function Test-ValidSha256 {
+    param([string] $Value, [string] $Label)
+    if ($Value -notmatch '\A[0-9a-f]{64}\z') {
+        Fail "$Label is not a 64-character lowercase hex value"
+    }
+}
+
+if ($CaCertPath) {
+    # Test seam only: trusts the loopback fixture's self-signed CA for this 
process without touching
+    # the real Windows certificate store. Production installs never set 
CAMEL_INSTALL_CA_CERT.
+    $installerCaCert = New-Object 
System.Security.Cryptography.X509Certificates.X509Certificate2($CaCertPath)
+    [System.Net.ServicePointManager]::SecurityProtocol = 
[System.Net.SecurityProtocolType]::Tls12
+    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {
+        param($sender, $certificate, $chain, $sslPolicyErrors)
+        $verifyChain = New-Object 
System.Security.Cryptography.X509Certificates.X509Chain
+        $verifyChain.ChainPolicy.ExtraStore.Add($installerCaCert) | Out-Null
+        $verifyChain.ChainPolicy.RevocationMode = 
[System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck
+        $verifyChain.ChainPolicy.VerificationFlags = 
[System.Security.Cryptography.X509Certificates.X509VerificationFlags]::AllowUnknownCertificateAuthority
+        $leaf = New-Object 
System.Security.Cryptography.X509Certificates.X509Certificate2($certificate)
+        if (-not $verifyChain.Build($leaf)) {
+            return $false
+        }
+        $root = $verifyChain.ChainElements[$verifyChain.ChainElements.Count - 
1].Certificate
+        return $root.Thumbprint -eq $installerCaCert.Thumbprint
+    }.GetNewClosure()
+}
+
+# Downloads $Url to $OutFile; used for both the manifest and archive fetches.
+function Save-RemoteFile {
+    param([string] $Url, [string] $OutFile)
+    try {
+        Invoke-WebRequest -Uri $Url -OutFile $OutFile -UseBasicParsing | 
Out-Null
+    } catch {
+        Fail "failed to download $Url"
+    }
+}
+
+# Reads $Path line by line without ever dot-sourcing, invoking, or evaluating 
its content.
+function Read-Manifest {
+    param([string] $Path)
+
+    $lines = @(Get-Content -LiteralPath $Path -Encoding UTF8)
+    if ($lines.Count -ne 4) {
+        Fail "manifest must contain exactly four lines"
+    }
+
+    $known = @('format', 'version', 'tar_sha256', 'zip_sha256')
+    $values = New-Object 
'System.Collections.Generic.Dictionary[string,string]' 
([StringComparer]::OrdinalIgnoreCase)
+    foreach ($line in $lines) {
+        if ([string]::IsNullOrEmpty($line)) {
+            Fail "manifest contains a blank line"
+        }
+        $parts = $line.Split('=', 2)
+        if ($parts.Count -ne 2 -or [string]::IsNullOrEmpty($parts[0])) {
+            Fail "manifest contains a blank line"
+        }
+        $key = $parts[0]
+        $value = $parts[1]
+        if ([string]::IsNullOrEmpty($value)) {
+            Fail "manifest key '$key' has an empty value"
+        }
+        if ($values.ContainsKey($key)) {
+            Fail "manifest has duplicate key: $key"
+        }
+        if ($known -notcontains $key.ToLowerInvariant()) {
+            Fail "manifest has unknown key: $key"
+        }
+        $values[$key] = $value
+    }
+
+    foreach ($required in $known) {
+        if (-not $values.ContainsKey($required)) {
+            Fail "manifest is missing a required key"
+        }
+    }
+
+    if ($values['format'] -ne '1') {
+        Fail "unsupported manifest format: $($values['format'])"
+    }
+    if (-not (Test-ValidVersion $values['version'])) {
+        Fail "manifest version is not a valid X.Y.Z value"
+    }
+    Test-ValidSha256 $values['tar_sha256'] 'manifest tar_sha256'
+    Test-ValidSha256 $values['zip_sha256'] 'manifest zip_sha256'
+
+    return $values
+}
+
+# Lists archive entries via System.IO.Compression before Expand-Archive ever 
runs, and rejects absolute
+# paths, traversal, symlink/reparse-point entries, multiple top-level roots, 
and a missing launcher.
+function Test-ArchiveEntry {
+    param([string] $ArchivePath, [string] $Version)
+
+    $expectedRoot = "camel-launcher-$Version"
+    Add-Type -AssemblyName System.IO.Compression.FileSystem
+    $zip = [System.IO.Compression.ZipFile]::OpenRead($ArchivePath)
+    try {
+        $roots = New-Object 'System.Collections.Generic.HashSet[string]'
+        $foundBatchLauncher = $false
+        foreach ($entry in $zip.Entries) {
+            $name = $entry.FullName
+            if ([string]::IsNullOrEmpty($name)) {
+                continue
+            }
+            if ($name.StartsWith('/') -or $name.StartsWith('\') -or 
($name.Length -ge 2 -and $name[1] -eq ':')) {
+                Fail "archive contains an absolute path entry: $name"
+            }
+            $normalized = $name.Replace('\', '/')
+            $segments = $normalized.Split('/')
+            if ($segments -contains '..') {
+                Fail "archive contains a path traversal entry: $name"
+            }
+            $unixMode = ([uint32]$entry.ExternalAttributes -shr 16) -band 
0xF000
+            if ($unixMode -eq 0xA000) {
+                Fail "archive contains a symbolic link or reparse point entry, 
which is not allowed"
+            }
+            [void]$roots.Add($segments[0])
+            if ($normalized -eq "$expectedRoot/bin/camel.bat") {
+                $foundBatchLauncher = $true
+            }
+        }
+        if ($roots.Count -ne 1) {
+            Fail "archive must contain exactly one top-level directory"
+        }
+        if (-not $roots.Contains($expectedRoot)) {
+            Fail "archive top-level directory does not match expected version: 
$($roots -join ',')"
+        }
+        if (-not $foundBatchLauncher) {
+            Fail "archive is missing bin\camel.bat"
+        }
+    } finally {
+        $zip.Dispose()
+    }
+}
+
+# Runs the freshly staged upstream launcher; a nonzero exit (e.g. no Java 17+ 
available) aborts the
+# install and leaves the previously active installation untouched.
+function Test-StagedLauncher {
+    param([string] $LauncherPath)
+    try {
+        & $LauncherPath 'version' *> $null
+    } catch {
+        Fail "staged launcher failed verification (Java 17+ required)"
+    }
+    if ($LASTEXITCODE -ne 0) {
+        Fail "staged launcher failed verification (Java 17+ required)"
+    }
+}
+
+function Set-CamelShim {
+    param([string] $Version, [string] $StagedRoot)
+
+    $targetDir = Join-Path $DataRoot $Version
+    New-Item -ItemType Directory -Force -Path $DataRoot | Out-Null
+    if (Test-Path -LiteralPath $targetDir) {
+        Remove-Item -LiteralPath $targetDir -Recurse -Force
+    }
+    Move-Item -LiteralPath $StagedRoot -Destination $targetDir
+
+    New-Item -ItemType Directory -Force -Path $BinDir | Out-Null
+    $launcherPath = Join-Path $targetDir 'bin\camel.bat'
+    $shimContent = "@echo off`r`ncall `"$launcherPath`" %*`r`nexit /b 
%ERRORLEVEL%`r`n"
+    $tempShim = Join-Path $BinDir ".camel.$PID.tmp.cmd"
+    # Write without a BOM: Windows PowerShell 5.1's 'Set-Content -Encoding 
UTF8' prepends one, which
+    # cmd.exe treats as part of the first line, breaking '@echo off' and 
emitting a stray error.
+    [System.IO.File]::WriteAllText($tempShim, $shimContent, (New-Object 
System.Text.UTF8Encoding($false)))
+    $finalShim = Join-Path $BinDir 'camel.cmd'
+    Move-Item -LiteralPath $tempShim -Destination $finalShim -Force
+}
+
+# Adds $Dir once, case-insensitively, to the current user's PATH 
(registry-level, no elevation) and to
+# this process; the machine PATH is never written. The value is read and 
written through the registry
+# with DoNotExpandEnvironmentNames so existing %VAR% references are preserved 
rather than flattened,
+# and the original value kind (typically REG_EXPAND_SZ) is kept intact.
+function Add-UserPath {
+    param([string] $Dir)
+
+    $key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')
+    try {
+        $userPath = $key.GetValue('Path', '', 
[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
+        $kind = if ($userPath) { $key.GetValueKind('Path') } else { 
[Microsoft.Win32.RegistryValueKind]::ExpandString }
+        $entries = @()
+        if ($userPath) {
+            $entries = $userPath.Split(';') | Where-Object { $_ -ne '' }
+        }
+        $present = $entries | Where-Object { $_.TrimEnd('\') -ieq 
$Dir.TrimEnd('\') }
+        if (-not $present) {
+            $newPath = if ($entries.Count -gt 0) { ($entries + $Dir) -join ';' 
} else { $Dir }
+            $key.SetValue('Path', $newPath, $kind)
+        }
+    } finally {
+        $key.Close()
+    }
+    if (($env:Path -split ';') -notcontains $Dir) {
+        $env:Path = "$env:Path;$Dir"
+    }
+}
+
+if ($Version -and -not (Test-ValidVersion $Version)) {
+    Fail "invalid -Version value: $Version (expected X.Y.Z)"
+}
+
+New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
+$stagingRoot = Join-Path $InstallRoot ("staging." + 
[Guid]::NewGuid().ToString('N'))
+New-Item -ItemType Directory -Path $stagingRoot | Out-Null
+
+try {
+    if ($Version) {
+        $manifestUrl = "$ManifestBaseUrl/$Version.properties"
+    } else {
+        $manifestUrl = "$ManifestBaseUrl/latest.properties"
+    }
+    $manifestFile = Join-Path $stagingRoot 'manifest.properties'
+    Save-RemoteFile -Url $manifestUrl -OutFile $manifestFile
+
+    $manifest = Read-Manifest -Path $manifestFile
+    $resolvedVersion = $manifest['version']
+
+    if ($Version -and $Version -ne $resolvedVersion) {
+        Fail "manifest version ($resolvedVersion) does not match requested 
version ($Version)"
+    }
+
+    $archiveUrl = 
"$MavenBaseUrl/$resolvedVersion/camel-launcher-$resolvedVersion-bin.zip"
+    $archiveFile = Join-Path $stagingRoot 
"camel-launcher-$resolvedVersion-bin.zip"
+    Save-RemoteFile -Url $archiveUrl -OutFile $archiveFile
+
+    $actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath 
$archiveFile).Hash.ToLowerInvariant()
+    if ($actualHash -ne $manifest['zip_sha256']) {
+        Fail "checksum mismatch for downloaded archive"
+    }
+
+    Test-ArchiveEntry -ArchivePath $archiveFile -Version $resolvedVersion
+
+    $extractDir = Join-Path $stagingRoot 'extract'
+    Expand-Archive -LiteralPath $archiveFile -DestinationPath $extractDir 
-Force
+
+    $stagedRoot = Join-Path $extractDir "camel-launcher-$resolvedVersion"
+    $stagedLauncher = Join-Path $stagedRoot 'bin\camel.bat'
+    Test-StagedLauncher -LauncherPath $stagedLauncher
+
+    Set-CamelShim -Version $resolvedVersion -StagedRoot $stagedRoot
+    Add-UserPath -Dir $BinDir
+
+    Write-Host "Installed Camel CLI $resolvedVersion to $(Join-Path $DataRoot 
$resolvedVersion)"
+} finally {
+    if (Test-Path -LiteralPath $stagingRoot) {
+        Remove-Item -LiteralPath $stagingRoot -Recurse -Force -ErrorAction 
SilentlyContinue
+    }
+}
diff --git a/static/install.sh b/static/install.sh
new file mode 100644
index 00000000..7879686f
--- /dev/null
+++ b/static/install.sh
@@ -0,0 +1,290 @@
+#!/bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -eu
+
+# Test seams only: production installs never set these, so the defaults below 
are always used.
+manifest_base_url="${CAMEL_INSTALL_MANIFEST_BASE_URL:-https://camel.apache.org/camel-cli/releases}";
+maven_base_url="${CAMEL_INSTALL_MAVEN_BASE_URL:-https://repo1.maven.org/maven2/org/apache/camel/camel-launcher}";
+ca_cert="${CAMEL_INSTALL_CA_CERT:-}"
+
+data_root="${XDG_DATA_HOME:-$HOME/.local/share}/camel-cli/versions"
+camel_cli_root="${XDG_DATA_HOME:-$HOME/.local/share}/camel-cli"
+bin_dir="$HOME/.local/bin"
+
+fail() {
+    echo "install.sh: $1" 1>&2
+    exit 1
+}
+
+is_valid_version() {
+    v="$1"
+    case "$v" in
+        '' | .* | *. | *..* | *[!0-9.]*) return 1 ;;
+    esac
+    # The case above guarantees digits and single dots only, with no leading, 
trailing, or doubled
+    # dot, so a valid X.Y.Z is exactly the value containing two dots (three 
non-empty components).
+    rest="$v"
+    dots=0
+    while :; do
+        case "$rest" in
+            *.*)
+                dots=$((dots + 1))
+                rest="${rest#*.}"
+                ;;
+            *)
+                break
+                ;;
+        esac
+    done
+    [ "$dots" -eq 2 ]
+}
+
+validate_sha256() {
+    value="$1"
+    label="$2"
+    [ ${#value} -eq 64 ] || fail "$label is not a 64-character lowercase hex 
value"
+    case "$value" in
+        *[!0-9a-f]*) fail "$label is not a 64-character lowercase hex value" ;;
+    esac
+}
+
+fetch() {
+    url="$1"
+    outfile="$2"
+    if command -v curl >/dev/null 2>&1; then
+        if [ -n "$ca_cert" ]; then
+            curl -fsSL --cacert "$ca_cert" -o "$outfile" "$url"
+        else
+            curl -fsSL -o "$outfile" "$url"
+        fi
+    elif command -v wget >/dev/null 2>&1; then
+        if [ -n "$ca_cert" ]; then
+            wget -q --ca-certificate="$ca_cert" -O "$outfile" "$url"
+        else
+            wget -q -O "$outfile" "$url"
+        fi
+    else
+        fail "curl or wget is required to install the Camel CLI"
+    fi
+}
+
+sha256() {
+    file="$1"
+    if command -v sha256sum >/dev/null 2>&1; then
+        sha256sum "$file" | awk '{print $1}'
+    elif command -v shasum >/dev/null 2>&1; then
+        shasum -a 256 "$file" | awk '{print $1}'
+    elif command -v openssl >/dev/null 2>&1; then
+        openssl dgst -sha256 "$file" | awk '{print $NF}'
+    else
+        fail "sha256sum, shasum, or openssl is required to verify the download"
+    fi
+}
+
+# Reads $manifest_file line by line without ever sourcing, dot-invoking, or 
eval-ing its content.
+parse_manifest() {
+    file="$1"
+    cr=$(printf '\r')
+    p_format=""
+    p_version=""
+    p_tar=""
+    p_zip=""
+    seen_format=0
+    seen_version=0
+    seen_tar=0
+    seen_zip=0
+    line_count=0
+    while IFS='=' read -r key value || [ -n "$key" ]; do
+        # Tolerate a manifest served with CRLF line endings, matching the 
PowerShell parser.
+        key="${key%"$cr"}"
+        value="${value%"$cr"}"
+        line_count=$((line_count + 1))
+        [ -n "$key" ] || fail "manifest contains a blank line"
+        [ -n "$value" ] || fail "manifest key '$key' has an empty value"
+        case "$key" in
+            format)
+                [ "$seen_format" -eq 0 ] || fail "manifest has duplicate key: 
format"
+                seen_format=1
+                p_format="$value"
+                ;;
+            version)
+                [ "$seen_version" -eq 0 ] || fail "manifest has duplicate key: 
version"
+                seen_version=1
+                p_version="$value"
+                ;;
+            tar_sha256)
+                [ "$seen_tar" -eq 0 ] || fail "manifest has duplicate key: 
tar_sha256"
+                seen_tar=1
+                p_tar="$value"
+                ;;
+            zip_sha256)
+                [ "$seen_zip" -eq 0 ] || fail "manifest has duplicate key: 
zip_sha256"
+                seen_zip=1
+                p_zip="$value"
+                ;;
+            *)
+                fail "manifest has unknown key: $key"
+                ;;
+        esac
+    done < "$file"
+
+    if [ "$seen_format" -ne 1 ] || [ "$seen_version" -ne 1 ] || [ "$seen_tar" 
-ne 1 ] || [ "$seen_zip" -ne 1 ]; then
+        fail "manifest is missing a required key"
+    fi
+    [ "$line_count" -eq 4 ] || fail "manifest must contain exactly four lines"
+
+    [ "$p_format" = "1" ] || fail "unsupported manifest format: $p_format"
+    is_valid_version "$p_version" || fail "manifest version is not a valid 
X.Y.Z value"
+    validate_sha256 "$p_tar" "manifest tar_sha256"
+    validate_sha256 "$p_zip" "manifest zip_sha256"
+}
+
+# Lists archive entries with 'tar -tzf' and rejects absolute paths, traversal, 
symlinks/hardlinks,
+# multiple top-level roots, and a missing expected launcher, before anything 
is extracted.
+validate_tar() {
+    archive="$1"
+    version="$2"
+    expected_root="camel-launcher-$version"
+
+    listing="$staging_dir/listing.txt"
+    tar -tzf "$archive" > "$listing" 2>/dev/null || fail "archive is not a 
valid tar.gz"
+
+    verbose_listing="$staging_dir/listing-verbose.txt"
+    # LC_ALL=C keeps tar's hard-link annotation as the literal 'link to' 
string; GNU tar localizes it.
+    LC_ALL=C tar -tvzf "$archive" > "$verbose_listing" 2>/dev/null || fail 
"archive is not a valid tar.gz"
+    # tar -tv renders symlinks as 'name -> target' and hard links as 'name 
link to target'. Matching
+    # those substrings could in theory over-reject a regular file whose name 
contains ' -> ' or
+    # ' link to '; that fail-closed bias is deliberate, the release archive 
never carries such names.
+    grep -E -- ' -> | link to ' "$verbose_listing" >/dev/null 2>&1 \
+        && fail "archive contains a symbolic or hard link entry, which is not 
allowed"
+
+    saw_entry=0
+    while IFS= read -r entry; do
+        [ -n "$entry" ] || continue
+        case "$entry" in
+            /*) fail "archive contains an absolute path entry: $entry" ;;
+        esac
+        case "$entry" in
+            *"../"* | ".." | *"/..") fail "archive contains a path traversal 
entry: $entry" ;;
+        esac
+        # Every entry must sit under the single expected top-level directory; 
a differing root means a
+        # stray directory or the wrong version, so reject it directly rather 
than collecting roots.
+        root="${entry%%/*}"
+        [ "$root" = "$expected_root" ] \
+            || fail "archive top-level directory does not match expected 
version: $root"
+        saw_entry=1
+    done < "$listing"
+
+    [ "$saw_entry" -eq 1 ] || fail "archive must contain exactly one top-level 
directory"
+
+    grep -Fqx "$expected_root/bin/camel.sh" "$listing" || fail "archive is 
missing bin/camel.sh"
+}
+
+# Runs the freshly staged upstream launcher; a nonzero exit (e.g. no Java 17+ 
available) aborts
+# the install and leaves the previously active installation untouched.
+verify_staged() {
+    staged_sh="$1"
+    chmod +x "$staged_sh" 2>/dev/null || true
+    "$staged_sh" version >/dev/null 2>&1 || fail "staged launcher failed 
verification (Java 17+ required)"
+}
+
+activate() {
+    version="$1"
+    staged_root_dir="$2"
+    target_dir="$data_root/$version"
+
+    mkdir -p "$data_root"
+    rm -rf "$target_dir"
+    mv "$staged_root_dir" "$target_dir"
+
+    mkdir -p "$bin_dir"
+    tmp_link="$bin_dir/.camel.tmp.$$"
+    rm -f "$tmp_link"
+    ln -s "$target_dir/bin/camel.sh" "$tmp_link"
+    mv -f "$tmp_link" "$bin_dir/camel"
+}
+
+requested_version=""
+while [ $# -gt 0 ]; do
+    case "$1" in
+        --version)
+            [ $# -ge 2 ] || fail "Usage: install.sh [--version X.Y.Z]"
+            requested_version="$2"
+            shift 2
+            ;;
+        *)
+            fail "Usage: install.sh [--version X.Y.Z]"
+            ;;
+    esac
+done
+
+if [ -n "$requested_version" ]; then
+    is_valid_version "$requested_version" || fail "invalid --version value: 
$requested_version (expected X.Y.Z)"
+fi
+
+staging_dir="$camel_cli_root/.staging.$$"
+cleanup() {
+    rm -rf "$staging_dir"
+}
+trap cleanup EXIT INT TERM
+
+mkdir -p "$camel_cli_root"
+rm -rf "$staging_dir"
+mkdir -m 700 "$staging_dir"
+
+if [ -n "$requested_version" ]; then
+    manifest_url="$manifest_base_url/$requested_version.properties"
+else
+    manifest_url="$manifest_base_url/latest.properties"
+fi
+
+manifest_file="$staging_dir/manifest.properties"
+fetch "$manifest_url" "$manifest_file" || fail "failed to download manifest 
from $manifest_url"
+
+parse_manifest "$manifest_file"
+version="$p_version"
+
+if [ -n "$requested_version" ] && [ "$requested_version" != "$version" ]; then
+    fail "manifest version ($version) does not match requested version 
($requested_version)"
+fi
+
+archive_url="$maven_base_url/$version/camel-launcher-$version-bin.tar.gz"
+archive_file="$staging_dir/camel-launcher-$version-bin.tar.gz"
+fetch "$archive_url" "$archive_file" || fail "failed to download archive from 
$archive_url"
+
+actual_tar_sha256="$(sha256 "$archive_file")"
+[ "$actual_tar_sha256" = "$p_tar" ] || fail "checksum mismatch for downloaded 
archive"
+
+validate_tar "$archive_file" "$version"
+
+extract_dir="$staging_dir/extract"
+mkdir -p "$extract_dir"
+tar -xzf "$archive_file" -C "$extract_dir"
+
+staged_root_dir="$extract_dir/camel-launcher-$version"
+verify_staged "$staged_root_dir/bin/camel.sh"
+
+activate "$version" "$staged_root_dir"
+
+case ":$PATH:" in
+    *":$bin_dir:"*) ;;
+    *) echo "Add $bin_dir to your PATH to use the 'camel' command." ;;
+esac
+
+echo "Installed Camel CLI $version to $data_root/$version"
diff --git a/update-camel-cli-manifest.sh b/update-camel-cli-manifest.sh
new file mode 100755
index 00000000..5afe5ad8
--- /dev/null
+++ b/update-camel-cli-manifest.sh
@@ -0,0 +1,131 @@
+#!/bin/sh
+#
+# update-camel-cli-manifest.sh - publish Camel CLI Launcher installers + 
manifests.
+#
+# Usage: ./update-camel-cli-manifest.sh <version> [--latest]
+#
+# 1. Fetches install.sh and install.ps1 from apache/camel main and writes them
+#    byte-identical to static/install.sh and static/install.ps1.
+# 2. Downloads camel-launcher-<version>-bin.{tar.gz,zip} from Maven Central,
+#    computes SHA-256, and writes 
static/camel-cli/releases/<version>.properties.
+# 3. With --latest, updates latest.properties, but only if <version> is newer.
+#
+# The manifest format (format=1, version, tar_sha256, zip_sha256) mirrors
+# WebsiteManifestGenerator.java in apache/camel and is parsed by the 
installers.
+set -eu
+
+MAIN_RAW="https://raw.githubusercontent.com/apache/camel/main/dsl/camel-jbang/camel-launcher/src/install";
+MAVEN_BASE="https://repo1.maven.org/maven2/org/apache/camel/camel-launcher";
+
+repo_root=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
+static_dir="$repo_root/static"
+releases_dir="$static_dir/camel-cli/releases"
+
+die() { echo "error: $*" >&2; exit 1; }
+usage() { echo "Usage: $0 <version> [--latest]" >&2; exit 2; }
+
+[ $# -ge 1 ] || usage
+version=$1
+shift
+update_latest=0
+while [ $# -gt 0 ]; do
+    case $1 in
+        --latest) update_latest=1 ;;
+        *) usage ;;
+    esac
+    shift
+done
+
+echo "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || die "version must be 
X.Y.Z: $version"
+
+sha256() {
+    if command -v sha256sum >/dev/null 2>&1; then
+        sha256sum "$1" | awk '{print $1}'
+    elif command -v shasum >/dev/null 2>&1; then
+        shasum -a 256 "$1" | awk '{print $1}'
+    else
+        die "need sha256sum or shasum"
+    fi
+}
+
+# 0 (true) if $1 is strictly newer than $2 (both X.Y.Z numeric).
+version_gt() {
+    i=1
+    while [ "$i" -le 3 ]; do
+        fa=$(echo "$1" | cut -d. -f"$i")
+        fb=$(echo "$2" | cut -d. -f"$i")
+        [ "$fa" -gt "$fb" ] && return 0
+        [ "$fa" -lt "$fb" ] && return 1
+        i=$((i + 1))
+    done
+    return 1
+}
+
+tmp=$(mktemp -d)
+trap 'rm -rf "$tmp"' EXIT INT TERM
+
+# --- 1. Vendor the installer scripts from apache/camel main ------------------
+mkdir -p "$static_dir"
+for script in install.sh install.ps1; do
+    echo "Fetching $script from apache/camel main"
+    curl -fsSL "$MAIN_RAW/$script" -o "$tmp/$script" \
+        || die "could not download $script from $MAIN_RAW/$script"
+    mv "$tmp/$script" "$static_dir/$script"
+    echo "  wrote static/$script"
+done
+
+# --- 2. Compute checksums from the Maven Central -bin archives ---------------
+tar_url="$MAVEN_BASE/$version/camel-launcher-$version-bin.tar.gz"
+zip_url="$MAVEN_BASE/$version/camel-launcher-$version-bin.zip"
+echo "Downloading $tar_url"
+curl -fsSL "$tar_url" -o "$tmp/archive.tar.gz" || die "could not download 
$tar_url"
+echo "Downloading $zip_url"
+curl -fsSL "$zip_url" -o "$tmp/archive.zip" || die "could not download 
$zip_url"
+tar_sha256=$(sha256 "$tmp/archive.tar.gz")
+zip_sha256=$(sha256 "$tmp/archive.zip")
+
+manifest_body() {
+    printf 'format=1\nversion=%s\ntar_sha256=%s\nzip_sha256=%s\n' \
+        "$version" "$tar_sha256" "$zip_sha256"
+}
+
+write_manifest() {
+    dest=$1
+    tmpf=$(mktemp "$tmp/manifest.XXXXXX")
+    manifest_body >"$tmpf"
+    mv "$tmpf" "$dest"
+    echo "  wrote ${dest#"$repo_root"/}"
+}
+
+mkdir -p "$releases_dir"
+version_file="$releases_dir/$version.properties"
+
+# --- 3. Per-version manifest (immutable) ------------------------------------
+if [ -f "$version_file" ]; then
+    if manifest_body | cmp -s - "$version_file"; then
+        echo "$version.properties already up to date"
+    else
+        die "$version.properties exists with different checksums; per-version 
manifests are immutable"
+    fi
+else
+    write_manifest "$version_file"
+fi
+
+# --- 4. latest.properties (forward-only) ------------------------------------
+if [ "$update_latest" -eq 1 ]; then
+    latest_file="$releases_dir/latest.properties"
+    if [ -f "$latest_file" ]; then
+        current=$(sed -n 's/^version=//p' "$latest_file")
+        if [ "$current" = "$version" ]; then
+            echo "latest.properties already at $version"
+        elif version_gt "$version" "$current"; then
+            write_manifest "$latest_file"
+        else
+            die "latest.properties already at $current (newer than $version); 
refusing to move backward"
+        fi
+    else
+        write_manifest "$latest_file"
+    fi
+fi
+
+echo "Done."


Reply via email to