【Nexus】オフラインのリポジトリサーバ構築

# はじめに
お仕事の都合上オフライン環境での仕事があり、都度パッケージを細々移動する日々を送っていましたがオフライン環境下でもパッケージ管理をしたいと思いリポジトリサーバを構築しました。

本記事ではポジトリサーバをNexus、Dockerを使用して構築する手順をまとめたので共有させていただきます。(今回作るリポジトリはpypiのみです)

※構築手順にフォーカスを当てているため、用語の解説等は基本的に省略しております。

---
# 動作環境
### 構成図
今回の構築範囲は赤枠部分です。(docker-reposは次回まとめる予定)
![pypi構築手順_nexus_repository.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/2a3856fd-1934-bfae-1fa5-daf672347ae6.png)

### 端末情報
※サーバ/クライアントPCにはDocker(docker-compose含む)は事前にインストールされていることを前提としています

- Nexusサーバ
  OS: CentOS Linux release 7.9.2009 (Core)
  Docker: version 26.0.0, build 2ae903e
  Docker Compose: version 1.29.1, build c34c88b2
- クライアントPC(Mac)
  機種: Apple M2
  OS: macOS Ventura 13.3
  Docker: version 24.0.6, build ed223bc
  Docker Compose: version v2.23.0-desktop.1

---
# Nexusサーバ初期設定
任意のディレクトリにnexus-dataとdocker-compose.ymlを作成する
```
$ mkdir ./nexus-data && chown -R 200 ./nexus-data

$ ls -l
-rw-rw-r--.  1 handson handson 279  4月 30 09:03 docker-compose.yml
drwxrwxr-x. 16     200 handson 265  4月 30 09:04 nexus-data
```

```docker-compose.yml
version: '3'
services:
  nexus:
    restart: always
    container_name: docker-nexus
    image: sonatype/nexus3
    environment:
      INSTALL4J_ADD_VM_PARAMS: -Xmx2g
    ports:
      - 8081:8081
      - 8082:8082
      - 8083:8083
    volumes:
      - ./nexus-data:/nexus-data
```

docker-composeにて起動
```
$ docker-compose up -d
Creating network "docker-nexus_default" with the default driver
Pulling nexus (sonatype/nexus3:)...
latest: Pulling from sonatype/nexus3
22ebf0e44c85: Pull complete
fa750a094ee3: Pull complete
1f4a0800cfef: Pull complete
e84ea3691eef: Pull complete
cdea008ff1c4: Pull complete
ee2d6100f273: Pull complete
eee9c13a3249: Pull complete
Digest: sha256:80b945be128a6466320df09589d73ed4a5dbf09f3495243281d264bbc643228c
Status: Downloaded newer image for sonatype/nexus3:latest
Creating docker-nexus ... done
```

ログを確認して下記出力が表示されることを確認
```
$ docker logs -f docker-nexus
-------------------------------------------------

Started Sonatype Nexus OSS 3.67.1-01

-------------------------------------------------
```

ブラウザにて動作確認
```
http://192.168.0.204:8081/
```
![スクリーンショット 2024-04-30 9.33.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/0465c444-93c7-c90b-0eb6-8ab26f65d6bb.png)

ウィンドウ右上の `Sign in` からログインする
adminユーザのパスワードを確認
```
$ more nexus-data/admin.password
bdfd409b-0c83-4dbb-9033-2749970a3080
```

ログイン後はお好みのパスワードに変更しましょう
`Configure Anonymous Access` は `Enable anonumous access` にする(匿名でのアクセスを禁止にする) 
![スクリーンショット 2024-05-01 11.30.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/1853e156-bc18-cc2d-9bc0-090dc1000099.png)

ログイン後は設定(Configulation)ボタンが表示されます
![スクリーンショット 2024-05-01 11.47.43のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/2da3b6ad-834d-578e-f435-b111b328867e.png)

---
# pypiリポジトリ構築
### pypi用のBlob Store作成
`Configulation(Repository) > Blob Stores > Create Blob Store`
下記設定を行い `Save` を選択
```
Type: File
Name: pypi-repos
Path: /nexus-data/blobs/pypi-repos
Soft Quota: Disable
```
![スクリーンショット 2024-05-01 12.47.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/65ed7cc3-bd4d-9505-0ec2-cde155b8c1bf.png)

### pypi(hosted)用のRepository作成
Configulation(Repository) > Repositories > Create repository > pypi(hosted)
下記設定を行いCreate repositoryを選択
```
Name: pypi-hosted
Online: チェック
Blob store: pypi-repos
Strict Content Type Validation: チェック
Deployment policy: Allow redeploy
Proprietary Components: チェックなし
```

### pypi(proxy)用のRepository作成
`Configulation(Repository) > Repositories > Create repository > pypi(proxy)`
下記設定を行い `Create repository` を選択
```
Name: pypi-proxy
Remote storage: https://pypi.org
Use the Nexus Repository truststore: チェックなし
Blocked: チェックなし
Auto blocking enabled: チェック
Maximum component age: 1440
Maximum metadata age: 1440
Blob store: pypi-repos
Strict Content Type Validation: チェック
Routing Rule: None
Not found cache enabled: チェック
Not found cache TTL: 1440
```

### pypi(group)用のRepository作成
`Configulation(Repository) > Repositories > Create repository > pypi(grop)`
下記設定を行い `Create repository` を選択
```
Name: pypi-repos
Online: チェック
Blob store: pypi-repos
Strict Content Type Validation: チェック
Member repositories: pypi-proxy, pypi-hosted
```

Repositoriesの状況
![スクリーンショット 2024-05-01 18.12.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/7142857a-3b70-fc0a-32a8-d28545fbdd11.png)
以上でWeb(GUI)の設定は完了

---
# クライアントPC(MAC)側でpythonの実行環境を構築
### pythonの実行環境を構築
クライアントの実行環境は公式のpythonイメージを使用します。
```
❯ docker run -itd --name python-offline-upload python:3.6.8 /bin/bash

❯ docker ps -a
CONTAINER ID   IMAGE                             COMMAND                   CREATED         STATUS         PORTS                    NAMES
d8c3838e411a   python:3.6.8                      "/bin/bash"                 5 minutes ago   Up 5 minutes                            python-offline-upload
```

オフライン環境でのパッケージのアップロードにはtwineを使用するため、これだけ先にインストールします
```
# docker exec -it python-offline-upload /bin/bash

# pip install --upgrade pip

# pip list
Package    Version
---------- -------
pip        21.3.1
setuptools 41.0.1
wheel      0.33.4

# pip install twine

# pip list | grep twine
twine               3.8.0
```

```
# tee ~/.pypirc <<EOF>/dev/null
[pypi]
repository: http://192.168.0.204:8081/repository/pypi-hosted/
username: admin
password: <Nexusサーバ初期設定時に指定したPW>
EOF

# ls -al ~/.pypirc
-rw-r--r-- 1 root root 104 May  1 12:11 /root/.pypirc
```

作業ディレクトリ作成後、オフライン環境で登録するパッケージをDLする
```
# mkdir -p /tmp/res

# pip download -d /tmp/res pandas

# ls -l /tmp/res
total 22068
-rw-r--r-- 1 root root 12376775 May  1 12:00 numpy-1.19.5-cp36-cp36m-manylinux2014_aarch64.whl
-rw-r--r-- 1 root root  9463387 May  1 12:00 pandas-1.1.5-cp36-cp36m-manylinux2014_aarch64.whl
-rw-r--r-- 1 root root   229892 May  1 12:00 python_dateutil-2.9.0.post0-py2.py3-none-any.whl
-rw-r--r-- 1 root root   505474 May  1 12:00 pytz-2024.1-py2.py3-none-any.whl
-rw-r--r-- 1 root root    11053 May  1 12:00 six-1.16.0-py2.py3-none-any.whl
```
ここまでで事前準備完了です。

---
### 【オフライン】Nexusへパッケージをアップロード
twineを使用して先ほどDLしたパッケージをアップロードしていきます。
```
# twine upload --repository pypi /tmp/res/*
Uploading distributions to http://192.168.0.204:8081/repository/pypi-hosted/
Uploading numpy-1.19.5-cp36-cp36m-manylinux2014_aarch64.whl
100%|█████████████████████████████████████████████████████████████████████████████████████████████████| 11.8M/11.8M [00:00<00:00, 18.5MB/s]
Uploading pandas-1.1.5-cp36-cp36m-manylinux2014_aarch64.whl
100%|█████████████████████████████████████████████████████████████████████████████████████████████████| 9.03M/9.03M [00:00<00:00, 19.9MB/s]
Uploading python_dateutil-2.9.0.post0-py2.py3-none-any.whl
100%|███████████████████████████████████████████████████████████████████████████████████████████████████| 237k/237k [00:00<00:00, 3.97MB/s]
Uploading pytz-2024.1-py2.py3-none-any.whl
100%|███████████████████████████████████████████████████████████████████████████████████████████████████| 520k/520k [00:00<00:00, 7.46MB/s]
Uploading six-1.16.0-py2.py3-none-any.whl
100%|██████████████████████████████████████████████████████████████████████████████████████████████████| 15.5k/15.5k [00:00<00:00, 313kB/s]
#
```

GUIで反映されているかを確認
`Browse > pypi-hosted`
![スクリーンショット 2024-05-02 1.33.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/a4194cdc-c540-4f86-56d9-0ab2a48fb2df.png)

アップロードはOK!

---
### 【オフライン】Nexusからパッケージをダウンロード
先ほどアップロードしたパッケージをDLできるかを確認していきます。
ダウンロード用のコンテナ環境を構築
```
❯ docker run -itd --name python-offline-dl python:3.6.8 /bin/bash

❯ docker exec -it python-offline-dl /bin/bash
```

まずは下記設定ファイルを作成していきます。
```
# tee /etc/pip.conf <<EOF>/dev/null
[global]
timeout = 60
index = http://192.168.0.204:8081/repository/pypi-repos/pypi
index-url = http://192.168.0.204:8081/repository/pypi-repos/simple

trusted-host = 192.168.0.204
EOF

# ls -l /etc/pip.conf
-rw-r--r-- 1 root root 196 May  1 12:30 /etc/pip.conf
```

先ほどアップロードした `pip` と `pandas` インストールします。
```
# pip install --upgrade pip
Successfully installed pip-21.3.1

# pip install pandas
Successfully built pandas numpy
Installing collected packages: numpy, pytz, six, python-dateutil, pandas
Successfully installed numpy-1.19.5 pandas-1.1.5 python-dateutil-2.9.0.post0 pytz-2024.1 six-1.16.0

# pip list
Package         Version
--------------- -----------
numpy           1.19.5
pandas          1.1.5
pip             21.3.1
```
依存関係のnumpy含めてインストールできてますね！:clap:

---
### 【オンライン】Nexusからパッケージをダウンロード
クライアントの実行環境はとオフライン同様のpythonイメージを使用します。
```
❯ docker run -itd --name python-online-dl python:3.6.8 /bin/bash

❯ docker ps -a
CONTAINER ID   IMAGE                             COMMAND                   CREATED         STATUS         PORTS                    NAMES
a2131d780bbb   python:3.6.8                      "/bin/bash"               25 seconds ago      Up 25 seconds                               python-online-dl
```

オンライン環境では `repository` は `pypi-proxy` にすることでNexus内に保持していないパッケージのみpypi.orgに問い合わせが行くようになります。
```
# docker exec -it python-online-dl /bin/bash

# tee /etc/pip.conf <<EOF>/dev/null
[global]
timeout = 60
index = http://192.168.0.204:8081/repository/pypi-repos/pypi
index-url = http://192.168.0.204:8081/repository/pypi-repos/simple

trusted-host = 192.168.0.204
EOF
```

Nexus内にないパッケージとして `colorama` をインストールしてみましょう。
```
# pip install colorama
Looking in indexes: http://192.168.0.204:8081/repository/pypi-repos/simple
Collecting colorama
  Downloading http://192.168.0.204:8081/repository/pypi-repos/packages/colorama/0.4.5/colorama-0.4.5-py2.py3-none-any.whl (16 kB)
Installing collected packages: colorama
Successfully installed colorama-0.4.5
```
Nexus内にないパッケージもインストールできていることを確認できましたね。

![スクリーンショット 2024-05-02 2.22.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187797/cab13d72-e170-8a42-e2ba-b02452c613cd.png)
GUIでも`pypi-proxy` 保持されています！:thumbsup:

---
# 参考
以下、参考にさせていただいたサイト情報です。

Qiita

https://qiita.com/kannkyo/items/5195069c65350b60edd9

公式関連

https://www.sonatype.com/products/sonatype-nexus-repository

https://hub.docker.com/r/sonatype/nexus3#testing-the-dockerfile