気まま研究所ブログ

ITとバイク、思ったことをてきとーに書きます。

UbuntuでBaGetを用いてNuGetサーバを構築する

f:id:AonaSuzutsuki:20210218175710p:plain

こんにちは。
長らく私のC#プロジェクトでは .Net Frameworkを用いており、外部依存ライブラリも直にdllを参照しておりました。
しかしながら、.Net Standardより複数のフレームワークでビルドが可能となり、そうすると依存ライブラリの管理が厄介になってきました。
そこで複数のフレームワークに対応させるためにNuGetを用いると良いのですが、プライベートなライブラリなだけあってあまりNuGet公式には公開したくないというのが本心です。
また、ローカルフィードもありますが、バージョンが重なるとその分nupkgファイルが増えてしまうのもあまりよくありません。
そこで、自宅のUbuntuサーバびNuGetサーバを構築しようというのが今回の内容です。

検証環境

項目 詳細
機体 Raspberry Pi 4 / VMware Workstation 16
OS Ubuntu Server 20.04.1 LTS
BaGet v0.3.0-preview4
.Net Core 3.1.404 / 3.1.406
Apache 2.4

.Net Core環境の構築

基本的にはUbuntu に .NET SDK または .NET ランタイムをインストールするの通り、リポジトリを追加してaptよりインストールできますが、なぜか当環境ではインストールできなかったので手動でインストールします。
あと32bitの場合もパッケージで提供されてないので手動インストールが必要です。
基本的にはパッケージからインストールできるならそちらを使いましょう。公式でも推奨されています。
なお、手動インストールについても同ページに書いてある通り(内容が削除されてました)ですが、もう少し詳細な手順で記載していきます。

.Net Coreのダウンロード

BaGetでは執筆当時「netcoreapp3.1」がターゲットフレームワークとして指定されているため、.Net Core 3.1 SDKをインストールします。

f:id:AonaSuzutsuki:20210218175512p:plain
Binariesより該当するアーキテクチャを選択し開きます。
するとこのようなページが開くので、「Direct link」をコピーします。

コピーできたら、サーバ側でwgetコマンドなどでダウンロードします。

$ wget -P ~/Downloads https://.../dotnet-sdk-3.1.404-linux-arm64.tar.gz

.Net Coreのインストール

ダウンロードできたら、アーカイブを展開してパスを通します。

まずは展開から。

$ sudo mkdir -p /usr/local/dotnet/dotnet3.1
$ sudo tar zxf ~/Downloads/dotnet-sdk-3.1.404-linux-arm64.tar.gz -C /usr/local/dotnet/dotnet3.1

続いてシンボリックリンクを作成します。

$ sudo ln -s /usr/local/dotnet/dotnet3.1/dotnet /usr/local/bin/dotnet

展開できたら、正しく動作するか確認してみましょう。

$ dotnet --version
3.1.404

バージョンが表示されればOK。
パスが異なる場合や、アーキテクチャが異なる場合はコマンドが見つからないなどのエラーが表示されます。

BaGetの構築

BaGetのインストール

BaGetのReleaseページより、最新版(執筆当時はv0.3.0-preview4)をダウンロードし、展開します。

$ wget -P ~/Downloads https://github.com/loic-sharma/BaGet/releases/download/v0.3.0-preview4/BaGet.zip
$ mkdir -p ~/bin/BaGet
$ unzip -d ~/bin/BaGet ~/Downloads/BaGet.zip

試しにBaGetを実行してみます。

$ cd ~/bin/BaGet
$ dotnet BaGet.dll

すると5000番ポートでウェブサーバが動作するので、「http://localhost:5000」を開くと見慣れたNuGet画面が表示されるはずです。

f:id:AonaSuzutsuki:20210218175531p:plain

ただ、コンソールオンリーのサーバでブラウザがない場合はlocalhostを開けないと思うので、curlなどでHTMLがダウンロードできていればOK。

$ curl http://localhost:5000

このサーバは直接公開しないので、ファイアウォールやポート転送などの設定はしなくて大丈夫です。

BaGetへAPIキーの設定

基本的には公式のConfigurationの通りで、そのほかにも設定があるのでこちらも合わせてご覧ください。

デフォルトだと誰でもパッケージをプッシュでき、削除などもできてしまいます
それではかなり困るので、特定の人だけプッシュできるようにしたいところです。
そこで出番なのがAPIキーです。 APIキーはいわゆるパスワードみたいなもので、予めサーバ側で設定しておくことでこのキーを知っている者だけがパッケージのプッシュや削除などをすることができます。

BaGetと同じディレクトリにある「appsettings.json」にてApiKeyの項目があるため、そこに任意のAPIキーを設定します。
APIキーの生成はRandom string generatorrandom.orgなどを使えば良いかと。

{
  "ApiKey": "XXXXXXXXXXX",
  "Urls": "http://*:5000",
  "PackageDeletionBehavior": "Unlist",
  "AllowPackageOverwrites": false,
...

BaGetの所有権を変更

今後Apacheで動作させるため、Apacheの権限に合わせておきます。

$ sudo chown -R www-data:www-data ~/BaGet

BaGetをサービスへ登録する

現段階ではBaGetを起動すると同期的に動作するためサーバの操作ができなくなります。
Screenなどを用いれば非同期動作も可能ですが、再起動のたびにやってられないですしサービスにしてしまいましょう。
サービスの作り方は公式の「Apache 搭載の Linux で ASP.NET Core をホストする」を参考にしました。
なお、今回はユーザディレクトリにインストールしているため、WorkingDirectoryのuserは適当な物に変更しましょう

$ sudo nano /etc/systemd/system/BaGet.service
[Unit]
Description=BaGet Service.

[Service]
WorkingDirectory=/home/user/bin/BaGet
ExecStart=dotnet BaGet.dll
Restart=always
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-baget
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production 

[Install]
WantedBy=multi-user.target

ちなみに、.Net Coreの手動インストールとパッケージインストールでは当然のことながらパスが違うので、パスが通っていない場合などはフルパスでないとdotnetが実行できません。
必要に応じてフルパスを確認しておきましょう。

$ which dotnet
/usr/local/bin/dotnet

ここまでできたらサービスを起動してちゃんと接続できたらOK。
必要に応じて有効化もしておくと再起動時に勝手に動いてくれるようになります。

$ sudo systemctl start BaGet
$ sudo systemctl enable BaGet

Apache 2による公開

Proxyの設定

Apache 2でBaGetサーバへ繋げるにはプロキシサーバを利用します。
confならどれでもいいと思いますが、当環境ではHTTPS + バーチャルホストを利用しているのでsites-available内部にあるHTTPS用のconfに書き加えました。

ProxyPreserveHost on
ProxyPass / http://localhost:5000/

で、実は厄介なのがBaGetでは内部のcssなどを「/css/~」というようにルートから始まるように指定されているため、「http://example.com/nuget」みたいなディレクトリ構造にするともれなく白いページが表示されるだけになります。
そのため、すでにサイトを公開している場合はルートをプロキシするわけにもいかないので「http://nuget.example.com」みたいにバーチャルホストを使わないと表示されません

また、パッケージプッシュの際にURLが返されるのですが、ProxyPreserveHostはoffにすると本来「http://nuget.example.com/api/v2/package」とならないといけないところが「http://localhost:5000/api/v2/package」が帰ってきてしまうためプッシュすることができません。
onにするとホスト名が渡されるので正しく動作します。

なお、プロキシを利用するには「mod_proxy」と「mod_proxy_http」が必要です。

$ sudo a2enmod proxy proxy_http

HTTPS環境でMethod Not Allowed

厄介なのが、HTTPSを使う場合です。
以上を設定していざプッシュしようと思うとMethod Not Allowedとよくわからないツレないメッセージを返されます。

error: Response status code does not indicate success: 405 (Method Not Allowed).

これはパッケージをプッシュする先に「https://nuget.example.com/v3/index.json」を指定することになりますが、実際は「http://nuget.example.com/api/v2/package」へ転送されます。
つまり、https -> httpになっちゃうわけなんですよ。
こうなると、HTTPSではプロキシを設定していてもHTTPのほうで設定していない場合だと、正しいサーバへパッケージをプッシュできずにエラーが出ます。

この場合はHTTP側にもプロキシを設定するか、いっそのことHTTP側にリダイレクト処理を入れてあげて、HTTPをHTTPSへ飛ばす設定をしてあげます。
今どきHTTPのまま公開するメリットって無いと思うので後者でいいかなと。

<VirtualHost *:80>
    ServerName nuget.example.com
    
    RewriteEngine on
    RewriteCond %{SERVER_NAME} =nuget.example.com
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]
</VirtualHost>

なお、これを動かすにはmod_rewriteが必要なので予め有効にしておきましょう。

$ sudo a2enmod rewrite

すると「http://nuget.example.com/api/v2/package」へ転送されたものが「https://nuget.example.com/api/v2/package」へ転送されるので正しく動作するはずです。

パッケージのプッシュなど

パッケージのプッシュ

ここまで設定できたらいよいよパッケージをプッシュしてみます。
基本的にはUploadページを開いてそこにあるコマンド通りにすればいいのですが、APIキーを設定している場合はオプションでAPIキーを指定します。

dotnet

> dotnet nuget push -s https://nuget.example.com/v3/index.json -k XXXXXXXXXXXXX package.nupkg 
...
パッケージがプッシュされました。

nuget

> nuget push -Source https://nuget.aonsztk.xyz/v3/index.json -ApiKey XXXXXXXXXXXXX package.nupkg
...
Your package was pushed.

プッシュできました旨のメッセージが出ていればOK。
あとはBaGetトップページをみるとパッケージが追加されてるはずです。
PacketとPowerShellGetは使ったことないからわからない。

パッケージの上書き

デフォルトではオフになっていますが、設定をした上で同じパッケージバージョンをプッシュすることでパッケージを上書きすることが可能です。
基本的にはバージョンをあげて行くのが常だと思いますが、Copyrightだとか説明を入れ忘れたとかでバージョンを変えずにあげ直したい場合がしばしばあります。
これをするとキャッシュを削除しないとダメだったりするので本当はやらないほうがいいんですが、プライベートなサーバなら別に問題ないでしょう。
なお、上書きするとダウンロード統計とかリセットされるのでその点も注意。

BaGet.dllの直下にある「appsettings.json」の「AllowPackageOverwrites」をtrueに変更します。

{
  "ApiKey": "XXXXXXXXXXX",
  "Urls": "http://*:5000",
  "PackageDeletionBehavior": "Unlist",
  "AllowPackageOverwrites": true,
...

あとはプッシュするだけ。

パッケージの削除

パッケージを削除する場合もpushとほぼ同じですが、ファイル名じゃなくてパッケージ名とバージョンを指定しなければならないところにだけ注意してください。

dotnet

> dotnet nuget delete -s https://nuget.example.com/v3/index.json -k XXXXXXXXXXXXX package 1.0.0

nuget

> nuget delete -Source https://nuget.example.com/v3/index.json -ApiKey XXXXXXXXXXXXX package 1.0.0