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):
Esto nos manadará un correo para validar el email. Pulsamos en el link
1.2 GENERAR CREDENCIALES
Bajo tu zona de usuario, Pulsar en la opcion “view account”
Pulsamos en Generar Token y guardamos el valor en un sitio seguro
Copiar el valor de username y del token
Expiracion
Siempre que quieras puedes regenerar y revocar el token.
Con esas credenciales configuramos la seccion
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 :
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:
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:
Pasado un rato, vemos en la pagina de soaytpe que lo han verificado
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>
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>
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:
Description
Descripcion básica del artefacto
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:
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>
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:
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>