i2scim

Restoring Maven Central publishing

i2scim no longer publishes to Maven Central. The active distribution mechanism is the multi-arch Docker image at docker.io/independentid/i2scim. This document is the recipe to restore publishing if a future maintainer wants to ship i2scim-core and/or i2scim-client as JAR libraries again.

Releasing the Docker image (active process)

The supported image is independentid/i2scim-universal on Docker Hub, pushed with ./build.sh -p. CI (.github/workflows/build-and-attest.yml) builds the multi-arch image on every run to validate the Dockerfile, but never pushes it anywhere. Before creating the release:

  1. Refresh the Chainguard JRE base-image digest. i2scim-server/src/main/docker/Dockerfile.jvm pins cgr.dev/chainguard/jre:latest by digest so builds are reproducible. Chainguard rebuilds the image daily, so re-resolve it on each release cycle so every release ships on the latest stable JRE:
    docker buildx imagetools inspect cgr.dev/chainguard/jre:latest
    

    Copy the top-level (multi-arch index) Digest: value into the FROM cgr.dev/chainguard/jre:latest@sha256:<digest> line, update the “Resolved” date in the comment above it, and confirm the image still builds and starts locally (./build.sh --tag <ver>, then docker run --rm independentid/i2scim-universal:<ver>).

  2. Bump the version in the POMs (./mvnw versions:set -DnewVersion=<ver>). The CI release build fails when the release tag and the POM version disagree.
  3. Add release notes under “Recent Updates” in docs/README.md.
  4. Merge to master, then publish a GitHub release tagged <ver> (CI refuses a release whose tag does not match the POM version).
  5. Push the Docker Hub image from the release commit: ./build.sh -p --tag <ver> builds independentid/i2scim-universal:<ver> for linux/amd64 and linux/arm64 and also tags it latest. This is the only step that publishes an image; CI does not push to Docker Hub or GHCR.

Why publishing was dropped

The 10-module Maven structure carried publishing infrastructure on every module — GPG signing, Sonatype staging, source/javadoc JAR generation, an OSSRH <distributionManagement> block — but only i2scim-core and i2scim-client are library-shaped. The rest produce a Quarkus application or are scaffolding. Publishing had not been actively used and was adding maintenance overhead to every POM. Dropping it simplified the build; this recipe lets it come back without research.

Artifacts that would publish if restored

Two artifacts only:

The application module (currently i2scim-server after slice 6 lands; was i2scim-universal before that) must NOT publish. Its POM should declare <maven.deploy.skip>true</maven.deploy.skip> to make accidental mvn deploy invocations safe.

Prerequisites

  1. GPG key for signing. Configured locally with gpg --list-secret-keys. The key ID is what you’ll pass as gpg.keyname.
  2. OSSRH credentials. A Sonatype OSSRH account with deploy rights to com.independentid. Stored in ~/.m2/settings.xml under server-id ossrh:
    <servers>
      <server>
        <id>ossrh</id>
        <username>YOUR_OSSRH_USERNAME</username>
        <password>YOUR_OSSRH_TOKEN</password>
      </server>
      <!-- Optional: if your GPG key uses a passphrase, store it under the keyname -->
      <server>
        <id>YOUR_GPG_KEYNAME</id>
        <passphrase>YOUR_GPG_PASSPHRASE</passphrase>
      </server>
    </servers>
    
  3. Released artifacts must satisfy Sonatype’s Central-sync requirements. The preserved <licenses>, <developers>, <scm>, and <url> blocks in the POMs already cover the metadata side. If the sync rules have evolved, check Sonatype’s current requirements before publishing.

XML to paste back

1. Parent pom.xml — <distributionManagement>

Add inside <project>, conventionally after <developers>:

<distributionManagement>
    <snapshotRepository>
        <id>ossrh</id>
        <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
    </snapshotRepository>
    <repository>
        <id>ossrh</id>
        <url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
    </repository>
</distributionManagement>

2. Parent pom.xml — <pluginManagement> entries

Add to the existing <build><pluginManagement><plugins> block:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-gpg-plugin</artifactId>
    <version>3.2.7</version>
</plugin>
<plugin>
    <groupId>org.sonatype.plugins</groupId>
    <artifactId>nexus-staging-maven-plugin</artifactId>
    <version>1.7.0</version>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <version>3.11.2</version>
    <configuration>
        <release>25</release>
    </configuration>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>3.3.1</version>
</plugin>

3. Parent pom.xml — release profile

Add as a sibling of <build>:

<profiles>
    <profile>
        <id>release</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-gpg-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>sign-artifacts</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>sign</goal>
                            </goals>
                            <configuration>
                                <gpgArguments>
                                    <arg>--pinentry-mode</arg>
                                    <arg>loopback</arg>
                                </gpgArguments>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.sonatype.plugins</groupId>
                    <artifactId>nexus-staging-maven-plugin</artifactId>
                    <extensions>true</extensions>
                    <configuration>
                        <serverId>ossrh</serverId>
                        <nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
                        <autoReleaseAfterClose>false</autoReleaseAfterClose>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

4. i2scim-core/pom.xml and i2scim-client/pom.xml

In each library module’s <build><plugins>, add the source and javadoc plugin executions:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <executions>
        <execution>
            <id>attach-sources</id>
            <goals>
                <goal>jar-no-fork</goal>
            </goals>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <executions>
        <execution>
            <id>attach-javadocs</id>
            <goals>
                <goal>jar</goal>
            </goals>
        </execution>
    </executions>
</plugin>

And add a per-module release profile that signs with the same GPG key the parent profile sets up:

<profiles>
    <profile>
        <id>release</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-gpg-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>sign-artifacts</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>sign</goal>
                            </goals>
                            <configuration>
                                <keyname>${gpg.keyname}</keyname>
                                <passphraseServerId>${gpg.keyname}</passphraseServerId>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.sonatype.plugins</groupId>
                    <artifactId>nexus-staging-maven-plugin</artifactId>
                    <extensions>true</extensions>
                    <configuration>
                        <serverId>ossrh</serverId>
                        <nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
                        <autoReleaseAfterClose>false</autoReleaseAfterClose>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

5. Application module — opt-out

In the application module’s POM (currently i2scim-server, after slice 6 of the consolidation refactor), add:

<properties>
    <maven.deploy.skip>true</maven.deploy.skip>
</properties>

This prevents mvn deploy -P release from accidentally pushing the app JAR to OSSRH. Only the libraries publish.

Releasing

Once the XML is restored:

# Snapshot deploy
mvn deploy -P release -Dgpg.keyname=YOUR_GPG_KEYNAME

# Staging release
mvn versions:set -DnewVersion=X.Y.Z
mvn clean deploy -P release -Dgpg.keyname=YOUR_GPG_KEYNAME
# Then close + release the staging repository at https://s01.oss.sonatype.org/

# Tag and push
git tag -s vX.Y.Z -m "Release vX.Y.Z"
git push origin vX.Y.Z

Notes