オフライン環境でdnf(yum)で"依存関係を解決"してインストールする【RHEL,OL,Alma,CentOS,Fedora】

:::note warn
※ バージョン8以降は、yumからdnfに置き換わりました。（yum-utilsを除く）
使い方は同じですので適宜dnf->yumに置き換えてください。
:::

# TL;TR;
本番環境と同じバージョンのパッケージと、インストールしたいパッケージを一緒にオンライン環境でダウンロードし、自作のdnfレポジトリを作成します。
zip化して、本番環境にアップロードし、自作のレポジトリを本番環境に登録します。
これにより、オフラインでも依存関係を解決しながら、`dnf install <package>`で依存関係を解決しながらインストールが可能です。

# 1. はじめに
社内(または顧客)のセキュリティポリシーにより本番環境をオンラインにできないが、dnfパッケージをインストールしたいということがあります。

特に予め様々なパッケージがインストールされたOS環境（ゴールドイメージ等）では、新しいパッケージをインストールするには、元の依存パッケージを考慮しなければなりません。
（例：お客様環境に予め運用監視ツールが入っている。複数のシステムが同じOS上で動作する。など）

:::note 
ここでは、「オフライン環境」は、「 **本番環境** 」と表記します。
オンライン、オフライン、オフライン、オンライン←可読性に欠けるためです
:::

# 2. 前提
- 本番環境は、dnfコマンドが使えること（OSがFedora系列であること）
- 本番環境は、踏み台経由などでファイルを転送できること
- dnfコマンドが使える**オンライン環境**(社用Windows内のWSL2など)があること
※本番環境とオンライン環境の**OS・バージョンは異なっても良い**

本記事では以下の条件での実行例を示します。
- 本番環境に存在しないglibcというパッケージをインストールすることを目標
- 本番環境は、Red Hat Enterprise Linux 9 (ver9.2)
- 本番環境は、予め様々なdnfパッケージがインストールされている
- 本番環境は、SFTPを用いて踏み台経由でファイルアップロードが可能
- オンライン環境は、WSL2上のAlma Linux 9 (ver9.5)

# 3. 概要
以下の手順で進めます。
1. 本番環境でインストール済みパッケージのリストを取得
1. 本番環境に必要なEPELリポジトリを有効化する（必要に応じて）
1. オンライン環境にパッケージをダウンロード
1. 自作レポジトリを作成
1. レポジトリをzip化し、本番環境に転送
1. 本番環境に自作レポジトリを展開
1. yumに自作レポジトリを登録
1. `dnf install <package>`を実行してインストール

# 4. 詳細手順
### 1. 本番環境で、インストール済みパッケージのリストをテキストで取得
以下のコマンドは、インストール済みパッケージをrpmパッケージの命名表記に並び替えて、スペース区切りで出力(または保存)します。
```shell
dnf list installed | awk '{split($1, a, "."); print a[1] "-" $2 "." a[2]}' | tr '\n' ' '
```

実行例　※実際はもっと多くのパッケージがあります。
```shell
# > dnf list installed
unzip.x86_64                         6.0-57.el9                       @System
zip.x86_64                           3.0-35.el9                       @System

# > dnf list installed | awk '{split($1, a, "."); print a[1] "-" $2 "." a[2]}' | tr '\n' ' '
unzip-6.0-57.el9.x86_64 zip-3.0-35.el9.x86_64
```

### 2. 本番環境に必要なEPELリポジトリを有効化する（必要に応じて）
今回は本番環境で使われるRHEL9のレポジトリをAlmaLinuxに登録します。
```shell
sudo dnf install epel-release
```
その他のレポジトリの登録方法・ダウンロードリンクは以下の記事を参考にしてください。

https://qiita.com/yamada-hakase/items/fdf9c276b9cae51b3633#3-epel%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%82%92%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%99%E3%82%8B

### 3. オンライン環境にインストール済みリスト・インストールしたいパッケージを指定してダウンロード
次のオプションを加えて`dnf install`コマンドを実行します。
- `--downloadonly`: インストールせずダウンロードのみ
- `--downloaddir=<path-to-your-dir>`: ダウンロード先のディレクトリ
- `--releasever=<production-os-version>`: 本番環境のOSのメジャーバージョン(RHEL9.2なら、`9`)
- `--installroot=<install-root-path>`: 代理ルート（本来のインストール先と異なる場所）を指定します。
オンライン環境にインストール済みパッケージが無視されなくなります。
- `--forcearch=<x86_64/noarch/i686/i386>`：(任意)インストールしたいパッケージのアーキテクチャが異なる場合にのみ指定します。
- `--enablerepo=<repolist>`: (任意)特定のレポジトリのみを使いたい場合に使用します。
- `--disablerepo=<repolist>`: (任意)特定のレポジトリのみを使いたい場合に使用します。
- `--repo=<repolist>`: (任意)特定のレポジトリのみを使いたい場合に使用します。

手順１で出力したパッケージリストと、インストールしたいパッケージを指定します。
```shell
# installroot用のディレクトリを作成
sudo mkdir /installroot
# 自作レポジトリ用のディレクトリを作成
sudo mkdir /offline

# 本番環境でコピーしたインストール済みリストと、インストールしたいパッケージ(glibc)を
# 一緒に指定してダウンロードします。
sudo dnf install --downloadonly --releasever=9 \
                 --downloaddir=/offline --installroot=/installroot \
                 unzip-6.0-57.el9.x86_64 zip-3.0-35.el9.x86_64 glibc
```
上記でインストールするパッケージが見つからない場合は、登録済みのレポジトリに対象のパッケージが含まれていないことが原因なので、手順２から必要なレポジトリを探してください。
非公開のパッケージ(有償のパッケージ等)は、ダウンロードできないのでリストから削除して実行してください。
ほとんどの場合、依存関係に問題があるのは公開パッケージの方ですから。

### 4. 自作レポジトリを作成
パッケージをダウンロードしたディレクトリから自作のレポジトリを作成します。
```shell
# yum-utilsをオンライン環境にインストール
sudo dnf install yum-utils -y

# createrepoコマンドを実行し、自作レポジトリを作成
sudo createrepo /offline
```
### 5. レポジトリをzip化し、本番環境に転送
```shell
sudo zip -q /offline
```
転送は、各々の方法で行います。私の場合は、WinSCPを使いました。

### 6. yumに自作レポジトリを登録
転送したzipファイルを任意の場所で展開します。
```shell
# 今回は、/usr/local/offlineに配置する
cd /usr/local
sudo unzip -q <path-to-zipfile>
```
自作レポジトリを次のように登録します。
`baseurl`に先ほどzipを展開したパスを指定します。
```shell
# .repoファイル名は自由です。今回はoffline.repo
# レポジトリ名は最初に指定します。今回は offline-repo
sudo cat <<EOF > /etc/yum.conf.d/offline.repo
[offline-repo]
name=Offline Repository
baseurl=file:///usr/local/offline
enabled=1
gpgcheck=0
EOF
```

### 7. `dnf install <package>`を実行してインストール
本番環境では、インターネット上のレポジトリにアクセスできないので、他のレポジトリを無効化する必要があります。
```shell
sudo dnf install glibc --disablerepo=\* --enablerepo=offline-repo
```

:::note warn
`--disablerepo`を先に、`--enablerepo`を後に記述してください。
逆だと、有効化した後にすべてのレポジトリを無効にしてしまいます。
:::

上記のようにタイプするのが面倒な場合は、例えば次のようにしてレポジトリを無効にできます。

```shell
sudo grep -l 'enabled=1' /etc/yum.repos.d/*.repo | \
sudo xargs -r sed -i 's/^enabled=1/enabled=0/'
```
これで次のように自作のレポジトリ内の任意のパッケージをインストールできます。

```shell
sudo dnf install glibc
```

以上

# 5. 本記事で紹介する方法以外の方法
- 依存関係が解消できなかったパッケージを1つ1つ調べながらwebサイトから入手する
- オンライン環境でdnfコマンドを用いてバージョンを指定してダウンロードする

ただし上記の方法では解決に非常に時間がかかります。

例えば、systemdのバージョン252がインストールされており、253が必要だとします。
しかし、以下に示すようにsystemdに依存するパッケージがあるため、それらも全て253にする必要があります。
```shell
[ ~]$ dnf list installed | grep systemd
systemd.x86_64                       252-46.el9_5.2.alma.1            @System
systemd-libs.x86_64                  252-46.el9_5.2.alma.1            @System
systemd-pam.x86_64                   252-46.el9_5.2.alma.1            @System
systemd-rpm-macros.noarch            252-46.el9_5.2.alma.1            @System
```
このように、依存パッケージに依存するパッケージの依存関係があることを踏まえると、この作業は途方もないものになります。
本記事で紹介する方法は、そのような煩わしさを解消することが可能です。

# 参考資料

https://qiita.com/yokra9/items/e8842dee2c42fc479931

https://qiita.com/takom16/items/a4f6100a0e0df62e4062