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

royteeuwen pushed a commit to branch feature/sling-cli-release-automation
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 4ebd9fae5f3150c4953e6176a44fb1cebaa2daf1
Author: Roy Teeuwen <[email protected]>
AuthorDate: Sat May 30 08:09:57 2026 +0200

    release docs: prerequisites, README workflow, release skill
    
    Document the full release flow (manual Maven steps + CLI steps) and the
    prerequisites. In particular, correct the GPG passphrase setup for
    maven-gpg-plugin 3.x: the passphrase is read from a server whose id is
    gpg.passphraseServerId (default "gpg.passphrase") and decrypted via
    settings-security.xml, not from a <gpg.passphrase> profile property.
    
    Add a self-contained sling-release skill that automates the process.
---
 README.md                     | 204 ++++++++++++++++++++++++++++++++++++++----
 skills/sling-release/SKILL.md | 180 +++++++++++++++++++++++++++++++++++++
 2 files changed, 368 insertions(+), 16 deletions(-)

diff --git a/README.md b/README.md
index 3334d40..8f17c15 100644
--- a/README.md
+++ b/README.md
@@ -9,17 +9,108 @@ This module is part of the [Apache 
Sling](https://sling.apache.org) project.
 This module provides a command-line tool which automates various Sling 
development tasks. The tool is packaged
 as a docker image.
 
-## Configuration
-
-To make various credentials and configurations available to the docker image 
it is recommended to use a docker env file.
-A sample file is stored at `docker-env.sample`. Copy this file to `docker-env` 
and fill in your own information.
+## Prerequisites
+
+Before releasing you need the following in place. These are split between the 
**Maven side** (used by
+`mvn release:prepare`/`release:perform` in the project being released) and the 
**CLI side** (used by
+this Docker tool).
+
+### 1. ASF credentials for the CLI (`docker-env`)
+
+To make credentials available to the docker image, use a docker env file. A 
sample file is stored at
+`docker-env.sample`; copy it to `docker-env` and fill in your own information. 
At minimum it must
+provide your ASF credentials (these are read by the CLI for Nexus, JIRA, 
Whimsy and the mailing
+lists):
+
+    ASF_USERNAME=your-apache-id
+    ASF_PASSWORD=your-apache-password
+
+### 2. GPG signing key
+
+* Generate a code-signing key and publish it: `gpg --send-keys <KEY_ID>` to 
`keys.openpgp.org`, and
+  add its public block to the Sling `KEYS` file at
+  <https://dist.apache.org/repos/dist/release/sling/KEYS> (PMC members can 
commit there directly).
+* Note the key id (e.g. `0A1B2C3D4E5F6789`) — it goes into `settings.xml` 
below.
+
+### 3. Maven `~/.m2/settings.xml`
+
+Recent Apache/Sling parent POMs use **maven-gpg-plugin 3.x**, which changed 
how the GPG passphrase is
+supplied. The old approach of a `<gpg.passphrase>` property in the 
`apache-release` profile is **no
+longer used** — the plugin now reads the passphrase from a **server** whose id 
is given by
+`gpg.passphraseServerId` (default: `gpg.passphrase`) and decrypts it via 
`settings-security.xml`.
+
+```xml
+<settings>
+  <servers>
+    <!-- ASF Nexus credentials (encrypted, see settings-security.xml) -->
+    <server>
+      <id>apache.snapshots.https</id>
+      <username>your-apache-id</username>
+      <password>{ENCRYPTED}</password>
+    </server>
+    <server>
+      <id>apache.releases.https</id>
+      <username>your-apache-id</username>
+      <password>{ENCRYPTED}</password>
+    </server>
+
+    <!-- maven-gpg-plugin 3.x reads the passphrase from THIS server id 
(gpg.passphraseServerId
+         defaults to "gpg.passphrase") and decrypts it via 
settings-security.xml. -->
+    <server>
+      <id>gpg.passphrase</id>
+      <passphrase>{ENCRYPTED}</passphrase>
+    </server>
+  </servers>
+  <profiles>
+    <profile>
+      <id>apache-release</id>
+      <properties>
+        <apache.availid>your-apache-id</apache.availid>
+        <!-- required so gpg does not try to open an interactive pinentry 
dialog -->
+        <gpg.pinentryMode>loopback</gpg.pinentryMode>
+        <!-- the signing key id from step 2 -->
+        <gpg.keyname>YOUR_KEY_ID</gpg.keyname>
+        <!-- SMTP host used by the parent POM's announcement tooling -->
+        <smtp.host>smtp.gmail.com</smtp.host>
+      </properties>
+    </profile>
+  </profiles>
+</settings>
+```
+
+Encrypt the passwords/passphrase with `mvn --encrypt-password` (server 
passwords) and store the master
+password in `~/.m2/settings-security.xml`:
+
+```xml
+<settingsSecurity>
+  <master>{ENCRYPTED_MASTER_PASSWORD}</master>
+</settingsSecurity>
+```
+
+## Building
+
+The Docker image (`apache/sling-cli:latest`) is produced by the `docker:build` 
goal of the
+[fabric8 docker-maven-plugin](https://dmp.fabric8.io/). The image bundles the 
project jar via the
+generated `*-app.slingfeature` descriptor; the `slingfeature-maven-plugin` 
resolves the project's
+own bundle from the reactor, so the image always contains the jar built in the 
same invocation.
+
+A single command builds (and tests) the project and the image:
+
+    mvn clean package docker:build
+
+No prior `mvn install` is needed. The `docker:build` execution is also bound 
to the `package`
+phase, so the CI build (`mvn package`) builds the image too.
+
+To confirm the image contains the expected commands:
+
+    docker run --env-file=./docker-env apache/sling-cli release help
 
 ## Launching
 
-The image is built using `mvn package`. Afterwards it may be run with
+After building, run the image with:
 
     docker run --env-file=./docker-env apache/sling-cli
-    
+
 This invocation produces a list of available commands.
 
 ## Commands
@@ -39,21 +130,102 @@ To select a non-default execution mode provide the mode 
as an argument to the co
 Note that for running commands in the `INTERACTIVE` mode you need to run the 
Docker container in interactive mode with a pseudo-tty 
 attached (e.g. `docker run -it ...`).
 
-Listing active releases
+### Release workflow
+
+The full release lifecycle has **manual Maven steps** (run in the project 
being released) and **CLI
+steps** (run via this Docker tool). The CLI commands take the numeric staging 
repository id via
+`-r`/`--repository` (e.g. `3103` for `orgapachesling-3103`). Append `-x AUTO` 
to actually perform an
+action (the default mode is `DRY_RUN`).
+
+#### Manual steps in the project being released (Maven)
+
+These are **not** part of this tool — run them in the module you are 
releasing, with the prerequisites
+from the section above configured:
+
+1. Make sure the parent POM is up to date and the build is green (`mvn clean 
verify`).
+2. Dry-run the release and check that only `<version>`/`<scm>` change:
+
+       mvn release:prepare -DdryRun=true
+       mvn release:clean
+
+3. Deploy a snapshot and confirm `META-INF/LICENSE` and `META-INF/NOTICE` are 
in the jar:
+
+       mvn deploy
+
+4. Prepare and perform the release (creates the tag, bumps to the next 
SNAPSHOT, signs and stages the
+   artifacts to Nexus):
+
+       mvn release:prepare
+       mvn release:perform
+
+   Note the staging repository id from the `release:perform` output (a line 
like
+   `…/orgapachesling-1087`). If it scrolls past, `release list` (below) shows 
open repos too.
+
+#### CLI steps (this tool)
+
+After `release:perform` has staged the artifacts, drive the rest with the CLI:
+
+0. **Find the staging repository id** if you did not capture it. `release 
list` shows every staging
+   repo with its `[open]`/`[closed]` state and description; a freshly staged 
one is `[open]`:
+
+       docker run --env-file=./docker-env apache/sling-cli release list
+
+1. **Close** the staging repository. The description is derived automatically 
from the staged POM's
+   `<name>` + `<version>` (e.g. _Apache Sling Feature Model Launcher 1.3.6_) 
by browsing the
+   repository content, so it works even though an open repository is not yet 
in the Lucene index:
+
+       docker run --env-file=./docker-env apache/sling-cli release 
close-staging --repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO
+
+2. **Verify** the artifacts' signatures, hashes and CI status:
+
+       docker run --env-file=./docker-env apache/sling-cli release verify 
--repository=$STAGING_REPOSITORY_ID
+
+3. **Generate the vote email**:
+
+       docker run --env-file=./docker-env apache/sling-cli release 
prepare-email --repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO
+
+4. After the 72h vote, **tally the votes** and generate the result email. PMC 
membership is detected
+   automatically from your ASF id: if you are a PMC member the email says you 
will copy the release to
+   the dist directory yourself; otherwise it asks a PMC member to perform the 
dist upload:
+
+       docker run --env-file=./docker-env apache/sling-cli release tally-votes 
--repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO
+
+5. **Finalize** the release (post successful vote). This runs, in order: 
promote to Maven Central,
+   create the next Jira version, release the current Jira version, and update 
the Apache Reporter:
+
+       docker run --env-file=./docker-env apache/sling-cli release finalize 
--repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO
+
+   When the current user is detected as a PMC member, `finalize` additionally 
publishes to
+   `dist.apache.org` (requires `subversion`, which is bundled in the image). 
The previous version to
+   remove from `dist/release` is deduced automatically from the directory 
contents, so no extra flag
+   is needed:
+
+       docker run --env-file=./docker-env apache/sling-cli release finalize 
--repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO
 
-    docker run --env-file=./docker-env apache/sling-cli release list
+   PMC membership is determined from your ASF id (via Whimsy). A non-PMC 
committer's `finalize` skips
+   the dist upload and the `tally-votes` result email asks a PMC member to 
perform it.
 
-Generating a release vote email
+If the vote does not pass, **drop** the staging repository:
 
-    docker run --env-file=./docker-env apache/sling-cli release prepare-email 
--repository=$STAGING_REPOSITORY_ID
-    
-Generating a release vote result email
+    docker run --env-file=./docker-env apache/sling-cli release drop 
--repository=$STAGING_REPOSITORY_ID --execution-mode=AUTO
 
-    docker run --env-file=./docker-env apache/sling-cli release tally-votes 
--repository=$STAGING_REPOSITORY_ID
-    
-Generating the website update (only diff for now)
+### Command reference
 
-       docker run --env-file=docker-env apache/sling-cli release 
update-local-site --repository=$STAGING_REPOSITORY_ID
+| Command | Description |
+|---------|-------------|
+| `release list` | List closed staging repositories |
+| `release close-staging -r <id>` | Close an open staging repo, setting the 
description from the staged POM |
+| `release verify -r <id>` | Download and verify artifact signatures, hashes 
and CI status |
+| `release prepare-email -r <id>` | Generate (and send) the `[VOTE]` email |
+| `release tally-votes -r <id>` | Count votes and generate the `[RESULT]` 
email (PMC membership auto-detected; non-PMC email asks a PMC member to do the 
dist upload) |
+| `release promote -r <id>` | Promote a closed staging repo to Maven Central |
+| `release update-dist -r <id>` | Move artifacts to `dist.apache.org` (PMC 
only); previous version auto-deduced, override with `--previous-version <v>` |
+| `release finalize -r <id>` | Promote + Jira + Reporter in one step; also 
updates `dist.apache.org` when you are a PMC member |
+| `release drop -r <id>` | Drop a staging repository (failed vote / cleanup) |
+| `release create-new-jira-version -r <id>` | Create the next Jira version and 
move unresolved issues |
+| `release release-jira-version -r <id>` | Mark the Jira version as released 
and close fixed issues |
+| `release update-reporter -r <id>` | Register the release with the Apache 
Reporter System |
+| `release update-local-site -r <id>` | Generate the website update (diff only 
for now) |
 
 ## Assumptions
 
diff --git a/skills/sling-release/SKILL.md b/skills/sling-release/SKILL.md
new file mode 100644
index 0000000..5d0252d
--- /dev/null
+++ b/skills/sling-release/SKILL.md
@@ -0,0 +1,180 @@
+---
+name: sling-release
+description: Automates releasing an Apache Sling project end-to-end using 
Maven and the sling-cli Docker tool. Use when the user wants to release a Sling 
project, start a release, perform any step of the Apache Sling release process, 
or asks about releasing/voting/promoting a Sling module.
+tools: Bash, Read, Edit, Write
+---
+
+This skill releases an Apache Sling module end-to-end by combining **Maven** 
steps (run in the
+project being released) with the **`apache/sling-cli`** Docker tool (this 
repo) for the
+Nexus/vote/finalize steps.
+
+## Overview
+
+Three phases:
+1. **Stage** — Maven prepares the release and stages signed artifacts to Nexus.
+2. **Vote** — close the staging repo, verify, send the `[VOTE]` email, wait 
72h, tally.
+3. **Finalize** — after a passing vote: promote to Maven Central, update JIRA 
+ Reporter, and (for
+   PMC members) publish to `dist.apache.org`.
+
+## Prerequisites (verify before starting)
+
+1. **Docker** is available, and the `apache/sling-cli` image is built/pulled.
+2. **`~/.apache-committer`** (or any env file) with ASF credentials, passed to 
the container:
+   ```
+   ASF_USERNAME=your-apache-id
+   ASF_PASSWORD=your-apache-password
+   ```
+3. **GPG signing key** published to `keys.openpgp.org` and added to the Sling 
`KEYS` file
+   (<https://dist.apache.org/repos/dist/release/sling/KEYS>).
+4. **`~/.m2/settings.xml`** configured for maven-gpg-plugin **3.x** (used by 
recent parent POMs). The
+   passphrase is read from a **server** whose id is `gpg.passphrase` (the 
`gpg.passphraseServerId`
+   default) and decrypted via `settings-security.xml` — **not** from a 
`<gpg.passphrase>` profile
+   property (that older approach is passed to gpg literally and fails):
+   ```xml
+   <servers>
+     
<server><id>apache.snapshots.https</id><username>ID</username><password>{ENC}</password></server>
+     
<server><id>apache.releases.https</id><username>ID</username><password>{ENC}</password></server>
+     <server><id>gpg.passphrase</id><passphrase>{ENC}</passphrase></server>
+   </servers>
+   <profiles><profile><id>apache-release</id><properties>
+     <gpg.pinentryMode>loopback</gpg.pinentryMode>
+     <gpg.keyname>YOUR_KEY_ID</gpg.keyname>
+     <smtp.host>smtp.gmail.com</smtp.host>
+   </properties></profile></profiles>
+   ```
+5. **`~/.m2/settings-security.xml`** with the `<master>` password used to 
decrypt the above.
+
+If any prerequisite is missing, stop and tell the user how to set it up before 
proceeding.
+
+## Running the CLI
+
+Use the Docker image directly. A `sling-cli` shell alias may exist but usually 
includes `-it` (TTY),
+which **fails in non-interactive contexts** like Claude Code — so do not rely 
on it:
+
+```bash
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
<command> [options]
+```
+
+- Default mode is `DRY_RUN` (shows what would happen). Add `-x AUTO` to 
execute, `-x INTERACTIVE` to confirm each step.
+- Always run a **DRY_RUN first** for any command that sends mail or mutates 
Nexus/dist, show the user the output, then re-run with `-x AUTO`.
+- `~` does not expand inside quoted docker args — use `$HOME`.
+
+## Determining the Java version (Maven steps)
+
+Build with the Java version required by the project's Sling parent POM (recent 
parents require Java
+17). If `mvnXX` wrappers are absent, set `JAVA_HOME` explicitly to an 
installed JDK
+(`/usr/libexec/java_home -V` to list).
+
+## Phase 1: Stage (Maven, in the project being released)
+
+1. **Parent POM current?** Confirm it's up to date and `mvn clean verify` is 
green.
+2. **Dry-run prepare**, then clean. Verify only `<version>`/`<scm>` differ in 
`pom.xml.tag`:
+   ```bash
+   mvn release:prepare -DdryRun=true && mvn release:clean
+   ```
+3. **Deploy snapshot**; confirm `META-INF/LICENSE` + `META-INF/NOTICE` are 
inside the jar:
+   ```bash
+   mvn deploy
+   ```
+4. **Prepare + perform** (creates the tag, bumps to next SNAPSHOT, signs + 
stages to Nexus):
+   ```bash
+   mvn release:prepare
+   mvn release:perform
+   ```
+   Capture the staging repo id from a `…/orgapachesling-NNNN` line. If it 
scrolled off, find it with
+   `release list` (below).
+
+## Phase 2: Vote
+
+### 2.0 — Find the staging repo id (if not captured)
+```bash
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
list
+```
+Lists every staging repo with its `[open]`/`[closed]` state. A freshly staged 
repo is `[open]`; the
+numeric suffix of `orgapachesling-NNNN` is the `REPO_ID`.
+
+### 2.1 — Close the staging repository
+```bash
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
close-staging -r <REPO_ID> -x AUTO
+```
+The description is auto-derived from the staged POM's `<name> <version>` (so 
it is correct even for an
+open repo not yet in the Lucene index). After closing, the repo shows as 
`[closed]`.
+
+### 2.2 — Verify artifacts
+```bash
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
verify -r <REPO_ID>
+```
+Confirm PGP signatures, SHA-1, MD5, and CI status all pass before voting.
+
+### 2.3 — Send the vote email (dry-run first!)
+```bash
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
prepare-email -r <REPO_ID>          # review
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
prepare-email -r <REPO_ID> -x AUTO  # send
+```
+
+### 2.4 — Wait 72h, then tally
+At least 72 hours on [email protected] and ≥3 binding (PMC) +1 votes are 
required.
+```bash
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
tally-votes -r <REPO_ID>            # review
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
tally-votes -r <REPO_ID> -x AUTO    # send [RESULT]
+```
+**PMC membership is auto-detected from your ASF id** — no flag needed. If you 
are a PMC member the
+result email says you will copy the release to the dist directory yourself; 
otherwise it asks a PMC
+member to do the dist upload.
+
+### If the vote FAILS
+```bash
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
drop -r <REPO_ID> -x AUTO
+git push --delete origin <TAG_NAME>   # e.g. 
org.apache.sling.feature.launcher-1.3.4
+git tag -d <TAG_NAME>
+# restart Phase 1 with a new version
+```
+
+## Phase 3: Finalize (after a passing vote)
+
+```bash
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
finalize -r <REPO_ID> -x AUTO
+```
+Runs in order: promote to Maven Central → (if you are a PMC member) update 
`dist.apache.org` →
+create next JIRA version & move unresolved issues → mark JIRA version released 
→ update Apache
+Reporter.
+
+- **PMC detection is automatic.** A PMC member's run also publishes to 
`dist.apache.org`; the previous
+  version to remove is **deduced from the dist/release directory**, so no 
`--previous-version` is
+  needed (override with `--previous-version X.Y.Z` only if necessary). A 
non-PMC run skips the dist
+  step (a PMC member handles it, as requested in the tally-votes email).
+
+### Update the website (optional)
+```bash
+docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli release 
update-local-site -r <REPO_ID>
+```
+Review the diff, then commit & push the sling-site changes separately.
+
+## Quick reference
+
+`<cli>` = `docker run --rm --env-file=$HOME/.apache-committer apache/sling-cli`
+
+| Command | When |
+|---------|------|
+| `<cli> release list` | List staging repos with `[open]`/`[closed]` state + 
description |
+| `<cli> release close-staging -r <ID> -x AUTO` | After `mvn release:perform` |
+| `<cli> release verify -r <ID>` | Before sending the vote email |
+| `<cli> release prepare-email -r <ID> -x AUTO` | Send the `[VOTE]` email |
+| `<cli> release tally-votes -r <ID> -x AUTO` | After 72h with ≥3 binding 
votes (PMC auto-detected) |
+| `<cli> release finalize -r <ID> -x AUTO` | All post-vote steps (dist 
included automatically for PMC) |
+| `<cli> release promote -r <ID> -x AUTO` | Promote to Maven Central 
(individual step) |
+| `<cli> release update-dist -r <ID> -x AUTO` | Update dist.apache.org (PMC 
only; prev version auto-deduced) |
+| `<cli> release drop -r <ID> -x AUTO` | Drop staging (failed vote / cleanup) |
+| `<cli> release create-new-jira-version -r <ID> -x AUTO` | Create next JIRA 
version |
+| `<cli> release release-jira-version -r <ID> -x AUTO` | Mark JIRA version 
released |
+| `<cli> release update-reporter -r <ID> -x AUTO` | Update Apache Reporter |
+| `<cli> release update-local-site -r <ID>` | Update JBake website content |
+
+## Execution notes
+
+When this skill is invoked:
+1. Confirm which phase the user is starting from (Stage / Vote / Finalize / 
Cancel) and the `REPO_ID` if past Phase 1.
+2. Verify the prerequisites above before any mutating step.
+3. For mail/Nexus/dist commands, run DRY_RUN first, show output, then `-x 
AUTO`.
+4. On failure, diagnose (often a missing prereq or wrong `REPO_ID`) and 
suggest a fix before retrying.
+5. Do not touch git state beyond what the documented steps require.

Reply via email to