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