Saltar a contenido

Publicando en Maven Central

1.Requisitos

1.1 CREAR CUENTA EN SONATYPE

Entrar al portal de central Sonatype y pulsar en SignIn (nunca logearse con Social Logins):

./images/maven-central-signin.png

Esto nos manadará un correo para validar el email. Pulsamos en el link

./images/maven-central-validateemail.png

1.2 GENERAR CREDENCIALES

Bajo tu zona de usuario, Pulsar en la opcion “view account”

./images/maven-central-publish.png

Pulsamos en Generar Token y guardamos el valor en un sitio seguro

./images/maven-central-publish.png

Copiar el valor de username y del token

Expiracion

Siempre que quieras puedes regenerar y revocar el token.

Con esas credenciales configuramos la seccion de nuestro settings.xml, configurandola como credenciales para el server central en la seccion servers:

./images/maven-central-settings.xml.png

Central vs osssrh

El servidor OSSRH ha sido durante mucho tiempo el referente, era el servicios legacy que hacia replica al maven Central. Este tutorial habla de como publicar en el Central directamente.

1.3 CREAR UN NAMESPACE

Un namespace es lo que conocemos comunmente como el groupId.

Es groupId es unico e identifica al publicador.

Es muy importante, porque no queremos que nadie publique librerias que no son de nuestra organizacion/empresa dentro de nuestro namespace (groupId), asi que debemos registrar y que Maven Central verifique la veracidad del mismo.

Veracidad del groupId/Namespace

La veracidad la comprueba a traves del dominio. Si queremos tener, por ejemplo, el groupId: <groupId>com.miempresa</groupId>, debemos ser propietarios del dominio miempresa.com. Esta validacion la realizará pidiendo añadir un registro TXT en nuestro DNS.

NAMESPACE PUBLICO GITHUB.com

Como el proposito de este tutorial es publicar una libreria personal que tenemos alojada en Github, no vamos a realizar la validacion del namespace a traves de DNS (ya que no somos propietarios de github.com) lo vamos a hacer añadiendo el formato de namespace de github:

io.github.{myuser}

Asi que vamos a nuestra cuenta en la web de Sonatype y en el menú de nuestro usuario pulsamos en Namespaces

Una vez creado queda pendiente para validacion :

./images/maven-central-namespace-creation.png

Validando nuestro repositorio en GITHUB

La web nos indica las instrucciones para validar nuestro usuairo en Github, para ello debes un repositorio vacio en tu cuenta con el nombre que te dicen.

Muestran una pantalla como esta indicando ese nombre:

./images/maven-central-verify-namespace.png

CREAMOS EL REPOSITORIO DE VALIDACION EN NUESTRA CUENTA DE GITHUB

Usamos el wizard web para crear el repositorio. No hace falta que tenga contenido, solo que exista:

./images/maven-central-create-repo-validation.png

Pasado un rato, vemos en la pagina de soaytpe que lo han verificado

images/maven-central-namespace-success.png

2.Preparando nuestra Lib

Una vez tenemos la autorizacion de Maven Sonatype y las credenciales para publicar nuestros artefactos, vamos a crear y configurar nuestro proyecto Maven para publicarlo.

2.1 GENERANDO UN PROYECTO MAVEN VACIO

Generamos un proyecto Maven, en este caso usando unos de los archetypes publicos:

mvn archetype:generate -DarchetypeGroupId=io.github.oliviercailloux -DarchetypeArtifactId=java-archetype
...

[INFO] Using property: version = 0.0.1-SNAPSHOT
Define value for property 'groupId': org.dppware           
Define value for property 'artifactId': lib-utils-json
Define value for property 'package' org.dppware.lib-utils-json: org.dppware.lib.utils.json
Confirm properties configuration:
version: 0.0.1-SNAPSHOT
groupId: org.dppware
artifactId: lib-utils-json
package: org.dppware.lib.utils.json

2.2 SECCIONES OBLIGATORIAS EN POM.XML

Necesitamos proporcionar la meta-información que será la mostrada por Sonatype acerca de tu artefacto en su directorio.

Para publicar artecfactos es necesario que proporciones al menos esta informacion:

  • licenses
  • developers
  • SCM
  • url
  • description

Licenses:

Yo he usado la generica de APACHE:

<licenses>
  <license>
    <name>The Apache License, Version 2.0</name>
    <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
  </license>
</licenses>
Developers:

Al menos uno deberia existir:

 <developers>
    <developer>
      <name>Daniel Peña</name>
      <email>danipenaperez@gmail.com</email>
      <organization>DPPWare</organization>
      <organizationUrl>http://www.danipenaperez.com</organizationUrl>
    </developer>
  </developers>
SCM

Informacion propia del SCM asociado a tu artefacto:

<scm>
  <connection>scm:git:git://github.com/danipenaperez/lib-utils-json.git</connection>
  <developerConnection>scm:git:ssh://github.com:danipenaperez/lib-utils-json.git</developerConnection>
  <url>http://github.com/danipenaperez/lib-utils-json/tree/master</url>
</scm>

URL

URL del repositorio, en mi caso:

    <url>https://github.com/danipenaperez/lib-utils-json</url>

Description

Descripcion básica del artefacto

    <description>Utilities to transform and manage JSON Objects</description>

2.3 SOURCES Y JAVADOC

Como buena practica, es bueno proporcionar los sources y el javadoc para que Sonatype pueda almacenarlos y servidorl .asi que añadimos estos plugins al pom.xml:

<build>
  ...
  <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.2.1</version>
                <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>
                <version>2.9.1</version>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
      ....
<build>

2.4 FIRMANDO NUESTROS ARTEFACTOS

Uno de los requisitos fundamentales es que nuestros artefactos esten firmados al publicarlos, para ello vamos a instalar en nuestro pc la utilidad GPG

Usaremos esta herramienta parar generar claves para la firma.

Con esas claves podremos hacer deploy desde nuestra maquina o tambien subirlas a github actions para que las use para firmar los artefactos.

Instalacion:

sudo apt-get install gnupg
y verificamos la instalacion
$ gpg --version
gpg (GnuPG) 2.2.19
libgcrypt 1.8.5
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /home/dpena/.gnupg
Algoritmos disponibles:
Clave pública: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cifrado: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
         CAMELLIA128, CAMELLIA192, CAMELLIA256
Resumen: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compresión: Sin comprimir, ZIP, ZLIB, BZIP2

Vamos a generar unas claves (La clave quedara tambien securizada en nuestro pc para poder recuperarla, ya que tiene caducidad de 2 años):

(Tienes mas informacion https://central.sonatype.org/publish/requirements/gpg/#listing-keys

gpg --gen-key
gpg (GnuPG) 2.2.19; Copyright (C) 2019 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Nota: Usa "gpg --full-generate-key" para el diálogo completo de generación de clave.

GnuPG debe construir un ID de usuario para identificar su clave.

Nombre y apellidos: Daniel Peña Perez
Dirección de correo electrónico: danipenaperez@gmail.com
Está usando el juego de caracteres 'utf-8'.
Ha seleccionado este ID de usuario:
    "Daniel Peña Perez <danipenaperez@gmail.com>"

¿Cambia (N)ombre, (D)irección o (V)ale/(S)alir? V
Es necesario generar muchos bytes aleatorios. Es una buena idea realizar
gpg: clave B83D1904C967D92D marcada como de confianza absoluta
gpg: creado el directorio '/home/dpena/.gnupg/openpgp-revocs.d'
gpg: certificado de revocación guardado como '/home/dpena/.gnupg/openpgp-revocs.d/68811F75E52DFC6591553C9........rev'
claves pública y secreta creadas y firmadas.

pub   rsa3072 2025-02-13 [SC] [caduca: 2027-02-13]
      68811F75E52DFC6591553C9.......
uid                      Daniel Peña Perez <danipenaperez@gmail.com>
sub   rsa3072 2025-02-13 [E] [caduca: 2027-02-13]

Securizacion de las claves en nuestro PC

Las claves generadas por GPG en nuestro pc iran asociadas al superuser de nuestro PC. Por eso encontraremos momentos donde la ejecucion de GPG nos pedirá introducir nuestras credenciales root.

Listamos las claves:

$ gpg --list-keys
/home/dpena/.gnupg/pubring.kbx
------------------------------
pub   rsa3072 2025-02-13 [SC] [caduca: 2027-02-13]
      68811F75E52DFC6591553C9.......
uid        [  absoluta ] Daniel Peña Perez <danipenaperez@gmail.com>
sub   rsa3072 2025-02-13 [E] [caduca: 2027-02-13]

2.5 DISTRIBUYENDO NUESTRA CLAVE PUBLICA

Cuando nuestro artefacto este publicado, los usuarios necesitaran validar el cifrado, asi que necesitamos publicar nuestra clave publica en los servidores publicos.

En la pagina de maven central nos indican que hay estos 3 servidores, asi que intentarmos publicarla en los 3

  • keyserver.ubuntu.com
  • keys.openpgp.org
  • pgp.mit.edu

Debemos indicar el id de la clave (ya que podemos tener varias), e mi ejemplo mi clave es 68811F75E52DFC6591553C9........ Puedes obtenerla asi

$ gpg --list-signatures --keyid-format 0xshort
/home/dpena/.gnupg/pubring.kbx
------------------------------
pub   rsa3072/0xC967D92D 2025-02-13 [SC] [caduca: 2027-02-13]
      68811F75E52DFC6591553C9.......
uid        [  absoluta ] Daniel Peña Perez <danipenaperez@gmail.com>
sig 3        0xC967D92D 2025-02-13  Daniel Peña Perez <danipenaperez@gmail.com>
sub   rsa3072/0x9427FD35 2025-02-13 [E] [caduca: 2027-02-13]
sig          0xC967D92D 2025-02-13  Daniel Peña Perez <danipenaperez@gmail.com>

Asi que publicamos en los 3 servers:

$ gpg --keyserver keyserver.ubuntu.com --send-keys 68811F75E52DFC6591553C9.......
gpg: enviando clave B83D1904C967D92D a hkp://keyserver.ubuntu.com

$ gpg --keyserver keys.openpgp.org --send-keys 68811F75E52DFC6591553C9.......
gpg: enviando clave B83D1904C967D92D a hkp://keys.openpgp.org

$ gpg --keyserver pgp.mit.edu --send-keys 68811F75E52DFC6591553C9.......
gpg: enviando clave B83D1904C967D92D a hkp://pgp.mit.edu

2.5 Firmando y publicando desde nuestra maquina

FIRMANDO

Podriamos firmar nuestros jar con pgp, pero existen plugins maven que hacen esta tarea tan tediosa de manera sencilla. Asi que añadimos el maven-gpg-plugin a la seccion plugins:

        <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-gpg-plugin</artifactId>
                <version>1.5</version>
                <executions>
                    <execution>
                    <id>sign-artifacts</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>sign</goal>
                    </goals>
                    </execution>
                </executions>
        </plugin>
Este plugin hara uso del gpg instalado en nuestra maquina y utilizará la primera clave que encuentre. Si tienes varias claves gpg consulta la documentacion del maven-gpg-plugin.

PUBLICANDO

Como lo va a llevar todo maven, debemos indicar la seccion distribution manager indicando los repositorios de maven central para snapshot y release:

Asi que añadimos al pom del proyecto:

<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>

Y usaremos el central-publishing-maven-plugin que esta asociado a la phase deploy para publicar en maven central.

Añadimos el plugin:

        <plugin>
                <groupId>org.sonatype.central</groupId>
                <artifactId>central-publishing-maven-plugin</artifactId>
                <version>0.7.0</version>
                <extensions>true</extensions>
                <configuration>
                    <publishingServerId>central</publishingServerId>
                    <waitUntil>published</waitUntil>
                </configuration>
        </plugin>

2.6 EJECUCION

Para publicar basta con ejecutar mvn clean deploy. En este proceso se ejecutaran todas las phases maven.

Recuerda que al ejecutarse el gpg-plugin se necesita acceder a nuestras claves guardadas y es posible que el proceso nos pida introducir la autorizacion root (o el usuario asociado) para hacer uso de la clave.

2.7 ACEPTAR LA PUBLICACION

Como ultimo paso debes ir a tu profile de artifactory y pulsar en el boton de publish de tu deployment:

Validate Publication

3.PROFILES

Como puedes imaginar, siempre que queramos hacer un mvn clean install no queremos estar firmando el artefacto, solo en fases de deploy, etc.. Por ello es recomendable encapsular la ejecucion de los plugins que hemos añadido en secciones anteriores a un profile maven, de manera que solo se ejecuten en el caso que nos interese realizar esas tareas.

Aqui dejo como ha quedado el pom.xml final con el profile (ci-cd):

mvn clean deploy -P ci-cd

<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>io.github.danipenaperez</groupId>
    <artifactId>lib-utils-json</artifactId>
    <version>0.0.1</version>
    <url>https://github.com/danipenaperez/lib-utils-json</url>
    <description>Utilities to transform and manage JSON Objects</description>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.release>17</maven.compiler.release>
    </properties>
    <licenses>
        <license>
            <name>The Apache License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
        </license>
    </licenses>
    <developers>
        <developer>
            <name>Daniel Peña</name>
            <email>danipenaperez@gmail.com</email>
            <organization>DPPWare</organization>
            <organizationUrl>https://github.com/danipenaperez</organizationUrl>
        </developer>
    </developers>
    <scm>
        <connection>scm:git:git://github.com/danipenaperez/lib-utils-json.git</connection>
        <developerConnection>
            scm:git:ssh://github.com:danipenaperez/lib-utils-json.git</developerConnection>
        <url>http://github.com/danipenaperez/lib-utils-json/tree/master</url>
    </scm>
    <!-- GENERAL BUILD-->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
            </plugin>
        </plugins>
    </build>


    <profiles>
        <profile>
            <id>ci-cd</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-source-plugin</artifactId>
                        <version>2.2.1</version>
                        <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>
                        <version>2.9.1</version>
                        <executions>
                            <execution>
                                <id>attach-javadocs</id>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>1.5</version>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>

                    <plugin>
                        <groupId>org.sonatype.central</groupId>
                        <artifactId>central-publishing-maven-plugin</artifactId>
                        <version>0.7.0</version>
                        <extensions>true</extensions>
                        <configuration>
                            <publishingServerId>central</publishingServerId>
                            <waitUntil>published</waitUntil>
                        </configuration>
                    </plugin>

                </plugins>

            </build>
        </profile>
    </profiles>


    <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>
    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.7</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.4.7</version>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.9.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>