====== Nexus ======
Система хранения артефактов, поддерживает множество форматов: maven, yum, npm, docker, helm, raw и другие.
Типы:
* hosted — располагающиеся локально на собственном хранилище
* proxy — ссылка на удалённый репозиторий, но кеширующиеся при выкачивании
* group — позволяет объединять несколько репозиториев в одну группу
В Nexus при создании репозитория типа maven2 спрашивается, какие типы артефактов будут в нём храниться: Snapshot, Release или Mixed. В мире Maven принято, что для разных этапов готовности продукта артефакт хранится в своём собственном репозитории.\\
Когда продукт сырой и мы ещё не готовы опубликовать готовую версию, но нужно её запустить и проверить (например, артефакт, который скомпилируется для прогона тестов на прикоммите), мы добавляем к имени версии постфикс ''-SNAPSHOT'', и тогда maven по умолчанию будет заливать в snapshot-репозиторий. Без этого постфикса все артефакты будут публиковаться в release-репозиторий.
===== Заливка =====
These are bare bones bash scripts to import a Nexus 2 Maven, NuGet or npm repository (and likely other file system based repos) into Nexus Repository 3. It imports artifacts into a Nexus Repository 3 Maven2, NuGet or npm hosted repo.\\
https://github.com/sonatype-nexus-community/nexus-repository-import-scripts/tree/master
Если заливать одну и ту же версию, то поведение Нексуса зависит от политики Deployment policy в настройках репозитория.
* Disable redeploy (по умолчанию) - если версия уже есть, то заливаться ничего не будет, даже если файл отличается.
* Allow redeploy - если файл той же версии, но сам по себе отличается, то он будет залит. Если файл тот же самый - заливаться не будет.
==== Maven ====
''NEXUS_REPO_USER'', ''NEXUS_REPO_PASS'' и прочие заносятся в переменные Gitlab.
testapp
testapp
${env.NEXUS_REPO_URL}/repository/${env.NEXUS_REPO_BACKEND_NAME}
testapp
${env.NEXUS_REPO_USER}
${env.NEXUS_REPO_PASS}
stages:
- release
# backend
upload-backend-release:
stage: release
only:
changes:
- backend/**/*
needs:
- build-backend-code-job
script:
- cd backend
# Вместо mvn package теперь mvn deploy
- >
mvn deploy -DskipTests
-Dmaven.repo.local=${CI_PROJECT_DIR}/.m2/repository
-s settings.xml -Dversion.application=${VERSION}
# frontend заливается через curl
upload-frontend-release:
stage: release
only:
changes:
- frontend/**/*
needs:
- build-frontend-code-job
script:
- cd frontend/dist
- tar czvf testapp-${VERSION}.tar.gz frontend
- >
curl -v -u "${NEXUS_REPO_USER}:${NEXUS_REPO_PASS}"
--upload-file testapp-${VERSION}.tar.gz
${NEXUS_REPO_URL}/${NEXUS_REPO_FRONTEND_NAME}/${VERSION}/testapp-${VERSION}.tar.gz
==== curl ====
Curl-ом можно заливать файлы в репозитории Maven2, YUM и RAW. Репозитории NuGet, NPM и Docker не поддерживают такую загрузку.
# В URL maven-releases - название репозитория, org/foo - группа org.foo, lunar-lang - имя компонента, 1.0.0 - версия.
curl -u admin:admin -T artifact.jar http://k3.workgroup:8081/repository/maven-releases/org/foo/lunar-lang/1.0.0/lunar-lang-1.0.0.jar
{{:service:pasted:20240702-190746.png}}
https://support.sonatype.com/hc/en-us/articles/115006744008-How-can-I-programmatically-upload-files-into-Nexus-3
Залить все файлы из каталога, сохранив структуру.
# Здесь: из каталога ~/cka в репозиторий http://k3:8081/repository/test, логин-пароль admin
# 2 замены - первая меняет путь на URL, вторая - пробелы на %20
find ~/cka -type f -print0 |xargs -0 bash -c 'for f; do i=$(echo ${f/$PWD/http://k3:8081/repository/test}); i=$(echo ${i// /%20}); curl -u admin:admin --upload-file "$f" "$i" ; done'
По мотивам https://stackoverflow.com/questions/20050910/replacing-a-part-of-file-path-in-exec
Powershell
$folder = '$env:userprofile\Downloads\nexusartifacts'
$repo = 'http://k3:8081/repository/test'
$cred = 'admin:admin'
$files = (dir $folder -Recurse -File).FullName
foreach ($f in $files) {
$url = $f.Replace("$folder","$repo").Replace('\','/')
"$f ->`n$url`n"
& curl.exe -ksu $cred -T $f $url
}
==== Docker ====
Предполагается, что Nexus опубликован просто по HTTP и сам запущен в Докере.
- Cоздать репозиторий ''docker (hosted)'', например, с именем docker, и обязательно в настройках прописать ему порт HTTP, отличный от веб-интерфейсного, например, 8082.
- Отредактировать docker-compose или Dockerfile, чтобы этот порт был доступен снаружи, и перезапустить контейнер.
- Cоздать в Нексусе пользователя для этого репозитория и дать ему права.
- Чтобы Докер работал с незащищённым репозиторием, нужно ему прописать в конфигурацию ''insecure-registries'', например, ''k3:8082''.
{
"registry-mirrors": [
"https://mirror.gcr.io"
],
"insecure-registries": [
"k3:8082",
"k3.workgroup:8082",
"k1:8082"
]
}
- Далее
# Дать права Докеру на файл
sudo chown $USER:docker /etc/docker/daemon.json
# Перечитать настройки (может, и restart)
sudo systemctl reload docker
# Залогиниться (Докер-клиент не поддерживает путей URL, только хост:порт)
docker login k3:8082
# Поставить тэг образу, который нужно залить
docker tag alpine:3.19 k3:8082/repository/docker/alpine:3.19
# Залить образ в Нексус
docker push k3:8082/repository/docker/alpine:3.19
https://www.devopsschool.com/blog/how-to-upload-and-download-docker-images-using-nexus-registry-repository/\\
https://help.sonatype.com/en/hosted-repository-for-docker--private-registry-for-docker-.html\\
https://help.sonatype.com/en/ssl-and-repository-connector-configuration.html
Заливка всех образов, имеющихся на машине, в Нексус. С тэгом ''latest'' будет заливаться как есть, так что, возможно, нужно отфильтровать их в ''awk'' и тэгировать руками, чтобы не было перезаписи при заливке.
# Логин в репу Нексуса
docker login k3:8082
# Адрес репы (без слэша в конце)
repo='k3:8082/repository/docker'
# Список образов (кроме ) с тэгами. egrep - если понадобится запустить ещё раз, а awk не раскрывает переменные в /.../
# egrep отфильтровывает образы с тэгами, начинающимися на host:port/
images=($(docker image ls | awk '!/REPOSITORY|/ {print $1":"$2}' |egrep -v ^.*:[[:digit:]]*/))
# Тэгировать и залить
for i in "${images[@]}" ; do docker tag $i $repo/$i && docker push $repo/$i ; done
==== Python ====
Используется twine. В конце URL необходим trailing slash, иначе будет ошибка при загрузке.
# Upload all files in the folder
$twine = "$env:localappdata\Programs\Python\Python312\Scripts\twine.exe" # если нет в PATH
& $twine upload --repository-url "http://k3:8081/repository/pypi-lib/" `
-u admin -p admin --non-interactive --skip-existing --disable-progress-bar `
C:\temp\pypi\*
Есть вариант прописывать реквизиты входа в файл ''.pypirc'', например,
[distutils]
index-servers = pypi
[pypi]
repository: http://k3:8081/repository/pypi-lib/
username: admin
password: admin
Тогда команда загрузки будет
# -r - [имя репы], прописанной в .pypirc
twine upload -r pypi C:\temp\pypi\*.whl
https://help.sonatype.com/en/pypi-repositories.html\\
https://twine.readthedocs.io/en/stable/\\
https://stackoverflow.com/questions/56592918/how-to-upload-the-python-packages-to-nexus-sonartype-private-repo
===== Скачивание =====
[Unit]
Description=testapp-backend
[Service]
User=jarservice
Environment=REPORT_PATH=/var/testapp/reports
Environment=LOG_PATH=/var/testapp/logs
Restart=always
ExecStart=/usr/bin/java \
-Dmyserver.basePath='/home/jarservice/' \
-Dmyserver.bindAddr='127.0.0.1' \
-Dmyserver.bindPort='8080' \
-Dmyserver.hostName='testapp' \
-jar '/home/jarservice/testapp.jar'
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
#!/bin/bash
# Скрипт валится при любой ошибке
set -xe
# Перезалить дескриптор сервиса на ВМ для деплоя
sudo cp -rf testapp-backend.service /etc/systemd/system/testapp-backend.service
sudo rm -f /home/jarservice/testapp.jar || true
# Перенос артефакта в нужную папку
curl -u ${NEXUS_REPO_USER}:${NEXUS_REPO_PASS} -o testapp.jar ${NEXUS_REPO_URL}/${NEXUS_REPO_BACKEND_NAME}/ru/company/project/devops/testapp/${VERSION}/testapp-${VERSION}.jar
sudo cp ./testapp.jar /home/jarservice/testapp.jar || true
# Обновить конфиг systemd
sudo systemctl daemon-reload
# Перезапустить сервис
sudo systemctl restart testapp-backend
deploy:
stage: deploy
script:
- scp ./backend/testapp-backend.service ${DEV_USER}@${DEV_HOST}:/home/${DEV_USER}/testapp-backend.service
- >
ssh ${DEV_USER}@${DEV_HOST} "
export "CURRENT_VERSION=${VERSION}";
export "VERSION=${VERSION}";
export "DEV_HOST=${DEV_HOST}";
export "NEXUS_REPO_URL=${NEXUS_REPO_URL}";
export "NEXUS_REPO_USER=${NEXUS_REPO_USER}";
export "NEXUS_REPO_PASS=${NEXUS_REPO_PASS}";
setsid /bin/bash -s " < ./backend/deploy.sh
==== Gradle ====
Копия дистрибутива в локальном Nexus (raw-репозиторий)
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
#distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionUrl=http\://login:P@ssw0rd@URL[:PORT]/repository/gradle/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
=== Аутентификация для gradlew ===
variables:
GRADLE_OPTS: "-Dgradle.wrapperUser=$NEXUS_LOGIN -Dgradle.wrapperPassword=$NEXUS_PASSWORD"
# Сборка на воркере
.build: &build
- cd $CI_PROJECT_DIR
# Если нет в вышестоящих переменных
# - export GRADLE_OPTS="-Dgradle.wrapperUser=$NEXUS_LOGIN -Dgradle.wrapperPassword=$NEXUS_PASSWORD"
- cd test-module
- chmod +x ./gradlew
- >
./gradlew build
-x test-metamodel:build
-P gitLabPrivateToken=$REGISTRY_TOKEN
# Сборка через ssh, нужно экранировать кавычки, иначе будет ошибка
# bash: line 0: export: `-Dgradle.wrapperPassword=[MASKED]': not a valid identifier
apiTests:
stage: test
script:
- cd $CI_PROJECT_DIR
- ssh dev-server
"export "GRADLE_OPTS=\"$GRADLE_OPTS\"";
cd /home/user/api-tests/test-module &&
git init &&
./gradlew -x test clean build publishMavenPublicationToMavenLocal -P gitLabPrivateToken=$REGISTRY_TOKEN"
В свойствах пишем URL без логина и пароля, они сами подхватятся из GRADLE_OPTS.
distributionUrl=http\://URL[:PORT]/repository/gradle/gradle-7.3.3-bin.zip
https://docs.gradle.org/current/userguide/gradle_wrapper.html#customizing_wrapper\\
https://stackoverflow.com/a/69401685
Для CI/CD. Дело в том, что при локальной сборке разработчиками у них нет данных для доступа в локальный репозиторий.\\
Это скрипт замены ссылки в файле ''/gradle/wrapper/gradle-wrapper.properties'', который меняет ссылку на локальную,\\
если в переменной ''GRADLE_OPTS'' есть значение ''wrapperUser''.
[[ $GRADLE_OPTS =~ wrapperUser ]] && \
sed -i 's#^\(distributionUrl=*\).*\/\(.*\)$#\1https\\://example.com/nexus/repository/gradle-zip/\2#' gradle/wrapper/gradle-wrapper.properties
# Было
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
# Стало
distributionUrl=https\://example.com/nexus/repository/gradle-zip/gradle-7.3.3-bin.zip
==== Python ====
Репозиторий pypi можно скачать curl-ом, а вот загружать им туда не получится, для этого нужен twine.\\
С иерархией каталогов, как в случае с maven, можно не заморачиваться и грузить все файлы в одну папку. При последующей выгрузке в nexus с помощью twine необходимая структура будет создана автоматически.
Powershell
$server = 'http://k3:8081'
$repo = 'pypi'
$cred = 'admin:admin'
$urls = "C:\temp\$repo.txt"
$dest = "C:\temp\$repo"
mkdir $dest -ErrorAction SilentlyContinue > $null
$repoUrl = "$server/repository/$repo"
# Make URLs list of packages
$packageList = curl.exe -ksu $cred "$repoUrl/simple/"
$versionsList = ($packageList -match 'a href' -replace '<.*?>').trim() |% {
(curl.exe -ksu $cred "$repoUrl/simple/$_/") -match 'a href'
}
# Packages or not packages
# ($versionsList -replace '.*\.\./(.*)#.*','$1') -notmatch '^packages'
$packages - $versionsList -replace '.*(packages.*)#.*','$1' -match '^packages/' # filter trash strings like
# If list restriction is needed, e.g. low disk space
#$letters = ($([char[]](0..255) -match '[u-z]') |% {"packages/$_"}) -join '|'
if ($letters) {
$packagesSelected = $packages -match "$letters"
}
else {
$packagesSelected = $packages
}
$packagesSelected |% {
"$repoUrl/$_"
} > $urls
# Download
gc $urls |% {
echo $_
curl.exe -ksu $cred -o "$dest\$($_ -replace '.*/')" $_
}
https://help.sonatype.com/en/pypi-repositories.html\\
https://stackoverflow.com/questions/50348248/creating-a-full-replica-offline-copy-of-the-public-pypi-repository
==== curl ====
Скачивание всего репозитория. В зависимости от типа репозитория (здесь пример для maven-репы), регулярку для sed надо корректировать.
server='http://k3:8081'
repo='repo'
cred='admin:admin'
urls='/tmp/repo_urls.txt'
# Get assets list
assets="$server/service/rest/v1/assets?repository=$repo"
i=$(curl -ksu $cred -X GET $assets)
grep downloadUrl <<< $i |cut -d\" -f4 > $urls
token=$(grep continuationToken <<< $i |cut -d\" -f4)
while [[ -n $token ]]; do
i=$(curl -ksu $cred -X GET "$assets&continuationToken=$token")
grep downloadUrl <<< $i |cut -d\" -f4 >> $urls
token=$(grep continuationToken <<< $i |cut -d\" -f4)
done
# Create subdirs
mkdir -p $(sed -E "s#.*$repo/(.*)/.*#\1#" $urls |sort |uniq)
# Download
for i in $(cat $urls); do
echo $i
curl -ksu $cred -o $(sed -E "s#.*$repo/##" <<< $i) $i
done
# Примеры ссылок в $urls для репозитория Docker
# http://k3:8081/repository/docker/v2/-/blobs/sha256:31eec910d005bbfe2c5618507337f97c56a4f9fc0767bd314786d3406d06b5e1
# http://k3:8081/repository/docker/v2/repository/docker/maven/manifests/3.5.2
# Регулярки для sed
mkdir -p $(sed -E 's#.*v2/(-/)?(.*)/.*#\2#' $urls |sort |uniq)
curl -ksu $cred -o $(sed -E 's#.*v2/(-/)?##' <<< $i) $i
https://www.sonatype.com/blog/nexus-repository-new-beta-rest-api-for-content\\
https://stackoverflow.com/questions/68055906/how-can-i-get-components-in-repository-via-nexus3-api\\
https://help.sonatype.com/en/pagination.html\\
https://www.steventwheeler.com/java/2018/10/30/migrate-artifactory-to-nexus.html\\
https://github.com/LoadingByte/nexus3-exporter/tree/master\\
===== Поиск =====
# Артефакт lunar-lang, сортировать по версиям
curl -qu admin:admin 'http://k3.workgroup:8081/service/rest/v1/search/assets?maven.artifactId=lunar-lang&sort=version'
# Определённая версия
curl -qu admin:admin 'http://k3.workgroup:8081/service/rest/v1/search/assets?maven.artifactId=lunar-lang&version=1.0.0'
# В определённом репозитории
curl -qu admin:admin 'http://k3.workgroup:8081/service/rest/v1/search/assets?maven.artifactId=lunar-lang&repository=maven-releases&version=1.0.1'
https://help.sonatype.com/en/search-api.html
Извлечение ссылок, Powershell
($json |ConvertFrom-Json).items.downloadUrl
# Вывод информации о найденных пакетах, сортировка по версиям
($json |ConvertFrom-Json).items.maven2 |sort {[version]$_.version}
extension groupId artifactId version
--------- ------- ---------- -------
jar org.foo lunar-lang 1.0.0
jar org.foo lunar-lang 1.0.1
jar org.foo lunar-lang 1.0.2
jar org.foo lunar-lang 1.0.3
Bash
# jq -r - без кавычек
$json |jq .items[].downloadUrl
==== Получить версию самой последней версии артефакта ====
Нужно, чтобы проставить версию в описании компонента при деплое. Надо парсить maven-metadata.xml
version=$(curl -qu admin:admin http://k3:8081/repository/maven-public/org/foo/lunar-lang/maven-metadata.xml |grep latest)
# В версиях встречаются цифры, точки и дефисы
sed 's#[^0-9.-]##g' <<< $version
1.0.12
===== Удаление =====
$apiUrl = 'http://k3:8081/service/rest/v1'
$cred = 'admin:admin'
(curl.exe -ksu $cred "$apiUrl/search/assets?repository=maven-public&version=1.0.?" |ConvertFrom-Json).items |% {
curl.exe -ksu $cred -X DELETE "$apiUrl/assets/$($_.id)"
}
==== Docker ====
[[https://support.sonatype.com/hc/en-us/articles/360009696054-How-to-delete-docker-images-from-Nexus-Repository-3|How to delete docker images from Nexus Repository 3]]\\
===== Установка Nexus =====
Пользователь id 200
docker run --rm -d --name nexus sonatype/nexus3
docker exec nexus grep nexus /etc/passwd
nexus:x:200:200:Nexus Repository Manager user:/opt/sonatype/nexus:/bin/false
==== Gitlab Authentication Realm ====
FROM sonatype/nexus3
USER root
RUN mkdir -p /opt/sonatype/nexus/system/com/github/oassuncao/ && \
curl -Lo /opt/sonatype/nexus/system/com/github/oassuncao/nexus-gitlab-plugin-1.6.3.jar https://github.com/oassuncao/nexus-gitlab-plugin/releases/download/v1.6.3/nexus-gitlab-plugin-1.6.3.jar && \
echo "reference\:file\:com/github/oassuncao/nexus-gitlab-plugin-1.6.3.jar = 200" >> /opt/sonatype/nexus/etc/karaf/startup.properties
USER nexus
https://github.com/oassuncao/nexus-gitlab-plugin
==== Миграция OrientDB > H2 ====
Nexus 3.70 - последняя версия, где поддерживается OrientDB. Чтобы обновить Nexus, нужно сконвертировать OrientDB в H2.
# Забэкапить базы из GUI, скопировать в папку backup.
# Остановить Nexus.
# Скачать мигратор
curl -L https://download.sonatype.com/nexus/nxrm3-migrator/nexus-db-migrator-3.70.1-03.jar -o backup/nexus-db-migrator.jar
# Запустить Nexus в командной строке
docker run -it --rm -v ./backup:/nexus-data -u root --entrypoint bash nexus
# В контейнере:
cd /nexus-data
java -Xmx16G -Xms1G -XX:+UseG1GC -XX:MaxDirectMemorySize=28672M -jar nexus-db-migrator.jar --migration_type=h2
# На хосте
cp backup/nexus.mv.db nexus-data/db/
echo "nexus.datastore.enabled=true" >> nexus-data/etc/nexus.properties
https://help.sonatype.com/en/migrating-to-a-new-database.html#migrating-from-orientdb-to-h2-162010\\
https://f3l1x-io.translate.goog/blog/2024/sonatype-nexus-repository-orientdb-a-h2?_x_tr_sl=auto&_x_tr_tl=ru&_x_tr_hl=ru&_x_tr_pto=wapp&_x_tr_hist=true
==== SSL ====
Исходный сертификат с ключом должен быть в формате PCKS12.
# Если исходник в формате PEM, его надо сконвертировать
openssl pkcs12 -export -in cert.pem -out cert.pkcs12
# Если исходник - это crt + key, то
openssl pkcs12 -export -in cert.crt -inkey private.key -out cert.pkcs12
Если исходник в виде .pfx, то конвертировать ничего не надо.
# Импортировать сертификат в хранилище ключей jks
# Важно: пароль оставить тем же, что и в сертификате
# Возможно, потребуется сначала создать каталог /nexus-/etc/ssl
keytool -v -importkeystore -srckeystore cert.pkcs12 -srcstoretype PKCS12 \
-destkeystore /nexus-/etc/ssl/keystore.jks -deststoretype JKS
nano /sonatype-work/nexus3/etc/nexus.properties
# Добавить строку
application-port-ssl=443
# Раскомментировать строку с nexus-args и добавить туда
${jetty.etc}/jetty-https.xml
nano /nexus-/etc/jetty/https-config.xml
# Заменить password на пароль сертификата (в 3 местах)
Перезапустить Nexus.
https://www.coveros.com/ssl-on-nexus-3/
===== Литература =====
[[https://help.sonatype.com/en/sonatype-nexus-repository.html|Документация]]\\
[[https://www.youtube.com/watch?v=6WU9nipfBGQ|Getting Started with Sonatype: The Best Way to Manage Your Java Releases]] (Daniel Persson on Youtube)