RaspberryPi4とDockerでグローバルアクセス可能な自前音楽ストリーミングサービスを作る【ざっくり解説】

# モチベーション
巷にはYouTubeMusic（旧GooglePlayMusic）とかSpotifyとか音楽ストリーミングサービスが色々あるが、いかんせん無課金状態だと使いにくいと感じる。これらのサービスの代替として使うべく、自身が保有する音楽ファイルと自宅内のDockerをインストールしたRaspberryPi4を使ってそこそこ手軽に**自分専用(※重要)の音楽ストリーミングサービス**サーバを構築したのでその手法を記す。以下の図が実際に作成したサービスの画面例である。
![キャプチャ5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352068/a63b45b4-2ffe-1c83-8fce-0401705ea8aa.png)


# 前提
dockerとdocker-composeの概要や基本コマンドを知っていることが望ましい。筆者は、書籍「[さわって学ぶクラウドインフラ　docker基礎からのコンテナ構築](https://bookmeter.com/books/16135808)」で学習したが、かなり理解しやすかったのでオススメ。

# 環境（ハード）
* **RaspberryPi4**
    * [この記事](https://qiita.com/camomile_cafe/items/54b1223e8acafe4cae50)などを参考にDockerとDocker Composeをインストールしておくこと。また、固定のローカルIPアドレスが振ってあること。
* **NAS**
    * 音楽ファイルを置く用。RaspberryPiのmicroSDカードの容量が多いならそこに置くのもアリ。
* **ブロードバンドルータ（以降BBRと表記）**
    * 大体どこのメーカでもよいはず。ポートフォワーディングの機能を使う。

# 環境（ソフト）
* **Emby**
    * 音楽ストリーミングサーバのWebアプリケーション。Embyが動作するサーバに任意の端末のWebブラウザでアクセスすると音楽がストリーミング再生される。公式によるDockerコンテナimageの提供がある。
* **https-portal**
    * HTTPSリバースプロキシサーバの機能を提供するDockerコンテナimage。
* **NO-IP**
    * DDNSサービス。自宅のBBRに振られているグローバルIP（G-IP）へ独自ドメインでアクセスできるようにする。本記事では無料利用できる機能を使う（後述する制約はあるが…）ので要ユーザ登録。

# 全体構成
自前音楽ストリーミングサーバの全体構成としては、次の図のようになる。
![My music server.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352068/a4a5529e-eba9-6b1b-3100-1c525107d4ac.png)

動作としては、
1. NO-IPのサービスで取得した自身の独自ドメインに、インターネット上の任意の端末からWebブラウザでアクセス（HTTPSで）
2. NO-IPにより独自ドメインがBBRのグローバルIPへ名前解決され、ブラウザから自宅のBBRへ到達
3. BBRのポートフォワーディング設定によりRaspberryPiにアクセスがフォワーディングされる
4. リバースプロキシコンテナでHTTPSが終端されEmbyコンテナにHTTPでバイパスされる
5. EmbyからのトラフィックはリバースプロキシまではHTTPで、リバースプロキシからHTTPSでブラウザまで運ばれる

なぜリバースプロキシ「https-portal」を挟んでいるかというと、Embyのアプリ内でのユーザ認証トラフィックがインターネット区間を通るためHTTPS化したかったということ、「https-portal」はHTTPS用の証明書取得・維持を自動でやってくれる点がかなり便利であること、将来的にEmby以外のサービスを動かしたときも手軽にHTTPS化ができる拡張性があるためである。

# 構築手順
大きな方針としては、まずローカルで動作するようにアプリ周りを作って外からのアクセス経路を作る。

###  【1】Embyコンテナの起動

ラズパイのログインユーザのホームディレクトリ(~)にプロジェクトディレクトリを作りdocker-composeファイルを配置する

```sh:
$ cd ~
$ mkdir music_server_project
$ cd music_server_project
$ touch docker-compose.yml
```
docker-compose.ymlの中身は次のように編集する。

```yml:docker-compose.yml(music_server)
version: "2.3"
services:
  emby:
    image: emby/embyserver_arm32v7
    container_name: embyserver
    environment:
      - UID=1000 # The UID to run emby as (default: 2)
      - GID=100 # The GID to run emby as (default 2)
      - GIDLIST=100 # A comma-separated list of additional GIDs to run emby as (default: 2)
    volumes:
      - ./config:/config # Configuration directory
      - ./share1:/mnt/share1 # Media directory
    ports:
      - 8096:8096 # HTTP port
    devices:
      - /dev/dri:/dev/dri # VAAPI/NVDEC/NVENC render nodes
      - /dev/vchiq:/dev/vchiq # MMAL/OMX on Raspberry Pi
    restart: always
```

docker-compose.ymlは、Embyコンテナ公式リファレンス記載の内容をRaspberryPi4で動くように微修正した。編集・保存後に.ymlのあるディレクトリで次のコマンドを実行するとコンテナが起動する。

```sh:
$ docker-compose up -d
```

プロジェクトフォルダ内には「config」「share1」ディレクトリが作成される。前者には、Embyアプリのコンフィグが保存されていくのでサーバ設定バックアップの際はここを保存する。後者は、音楽ファイルの置き場所で、音楽ファイルを直接置くか、またはNASのフォルダをここにマウントすることで音楽ファイルが入っている状態にすること。そして、Embyコンテナはポート8096でHTTP通信を待ち受けているので、RaspberryPi4の同ポートをマッピングしている。
コンテナ起動後に「 http://[ RaspberryPi4のIP ]:8096 」を同NW内のPCやスマホのブラウザに打ち込んでEmbyに接続できるか確認すること。

Embyの詳細なセットアップ手順は割愛するが、少なくとも次の3点は行う必要がある。
（1）管理者でログインして設定画面より音楽ファイルの置き場所をライブラリ登録する（下図参照）。前述したRaspberryPi4の「share1」フォルダは、Embyコンテナ内の「/mnt/share1」にバインドされているのでそのフォルダをライブラリとして指定している。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352068/71d3d9e1-e284-db15-283d-4be1fdf422d4.png)

（2）後々Embyにインターネット接続するようになるため、設定画面よりグローバルIPからのアクセスを許可しておく（下図参照）
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352068/a445652d-e31d-204b-a19b-7696259b6da5.png)
※ちなみに、このネットワーク設定画面では、IPアドレスフィルタや外部ドメインやサーバ証明書が設定できる項目もあるが、今回の構成ではリバースプロキシにその対応を切り出して担当してもらっているので設定する必要はない。

（3）普段のストリーミング視聴時に利用するための一般ユーザも作成しておく。

これで、ひとまずEmbyのローカル環境でのセットアップは完了である。（その他の細かい点は各自設定項目をカスタマイズされたし）

###  【2】No-IPでのドメイン申請

[公式ページ](https://www.noip.com/)でメアドとパスワードからユーザ登録を行う。以下図にあるように画面左の「DDNS→No-IP Hostnames」から「Create Hostname」を選択し、任意の名前でドメイン作成を行う。筆者の例では、ドメインとして「ddens.net」を使っているが、別のものも選択肢から選べる。注意点としては、無料ユーザでは、ドメインの作成は3つまで、各ドメインは30日ごとに期限が切れるという制約がある。後者については、メアドにリマインドが飛んでくるので、サイト上で利用継続のボタンを押すことで期限を延長する必要がある。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3335323036382f38393361353466332d353338662d373163612d616334302d3733393632373635343765342e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352068/19c3ee58-906e-6f20-be43-606cc95aada8.png)


ここで作成した独自ドメインは、リバースプロキシの設定で使用するので控えておくこと。

###  【3】リバースプロキシコンテナhttps-portalの起動

ラズパイのログインユーザのホームディレクトリ(~)にプロジェクトディレクトリを作りdocker-composeファイルを配置する。

```sh:
$ cd ~
$ mkdir tls_rproxy_project
$ cd tls_rproxy_project
$ touch docker-compose.yml
```
docker-compose.ymlの中身は次のように編集・保存する。

```yml:docker-compose.yml(tls_proxy_server)
version: "2.3"

services:
  https-portal:
    image: steveltn/https-portal
    container_name: https-portal
    ports:
      - '80:80'
      - '443:443'
    restart: always
    environment:
      ACCESS_RESTRICTION: ‘xxx.xxx.xxx.xxx/16 yyy.yyy.yyy.yyy/16’
      DOMAINS: 'No-IPで取得した独自ドメイン -> http://ラズパイのローカルIP:8096' # use service_name or privateIP
      STAGE: 'production' # Don't use production until staging works
      # FORCE_RENEW: 'true'
    volumes:
      - ./config:/var/lib/https-portal
```

パラメータとして指定しているACCESS_RESTRICTIONは、このリバプロコンテナでアクセスを許可したい送信元グローバルIPアドレスをスペース区切りで記述している（つまりホワイトリスト）。ここで記述したIP以外での全てのアクセスをリバプロで遮断できるので、自身の出先からのアクセスに使うグローバルIPが限定できる場合はそれを記述しておくほうが安心。
DOMAINSは、このリバプロでHTTPSを終端する独自ドメインとその転送先を設定しており、それぞれNo-IPで取得した独自ドメイン、ラズパイで動作しているEmbyのURLを記述する。なお、DOMAINSによる転送設定は「, 」区切りで複数指定することが可能である。その他、詳しいコンテナの動作仕様詳細を知りたい場合は、[作者github](https://github.com/SteveLTN/https-portal#multiple-domains)のREADMEを参照されたい。
volumesでは、プロジェクトディレクトリ直下のconfigディレクトリにコンテナの設定ファイルの格納パスがバインドマウントされるように記述している。
.ymlファイルの作成後、Embyコンテナ同様、docker-compose upコマンドで起動すること。https-portalコンテナでは、内部で[Let's Encrypt](https://letsencrypt.org/ja/about/)という無料でTLS通信用のWebサーバ証明書を発行してくれる認証局を利用している。コンテナが起動することで自身が作成した独自ドメイン用の証明書が認証局から取得され、さらにコンテナが起動している間は有効期限を自動で更新し続けてくれるため、TLS証明書管理が全て自動化され大変便利である。


###  【4】BBRのポートフォワーディング設定

最後に自宅のBBRのポートフォワーディング設定を行う。これは機種により設定方法は様々であるので説明は割愛するが、要約すると以下の2点を設定すれば良い。
（1）「BBRのWAN側グローバルIPアドレス宛に来たTCP80ポートへの通信」を「BBRのLAN側に接続されているラズパイIP宛のTCP80ポート」へ転送
（2）「BBRのWAN側グローバルIPアドレス宛に来たTCP443ポートへの通信」を「BBRのLAN側に接続されているラズパイIP宛のTCP443ポート」へ転送

###  【5】コンテナ状態確認
以上で、構築作業は完了である。作成した2つのコンテナの起動状態（STATUS）がUpになっていることを以下のコマンドで確認しておくこと。

```sh:
$ docker ps
CONTAINER ID   IMAGE                     COMMAND   CREATED       STATUS       PORTS                                                  NAMES
da93fd21b6b0   steveltn/https-portal     "/init"   2 weeks ago   Up 2 weeks   0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp               https-portal
60e29c657988   emby/embyserver_arm32v7   "/init"   6 weeks ago   Up 6 weeks   1900/udp, 7359/udp, 0.0.0.0:8096->8096/tcp, 8920/tcp   embyserver

```

# 動作確認

構築手順を全て実施後、例えばキャリアのモバイル通信を利用する状態（Not自宅のWifi接続状態）になっているスマホから、構築手順【2】で作成した独自ドメイン（https://をつけること）にWebブラウザでアクセスすると、自宅内からhttpの8096ポートでアクセスするのと同様にEmbyのアプリページにアクセスし、音楽ストリーミングができるはずである。このとき、TOPページのログイン画面がリーバスプロキシコンテナによりTLS暗号化されてることに注目されたい。これによりHTTPSでログイン情報を通信できる。
![キャプチャ3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352068/202140ea-2963-58be-8d45-af34ab2e343c.png)


また、AndroidOSでは、WebブラウザであるChromeでEmbyを開いた状態で右上ハンバーガー（？）メニューから「ホーム画面に追加」の項目を選択すれば、ホーム画面にEmbyのアイコンが追加される。このアイコンからEmbyを起動すると、WebアプリなのにChromeのヘッダ等がない画面で操作でき、かつスリープ時もバックグラウンドで再生が続くといった、まるでAndroidアプリのような振る舞いで動作するのでオススメである。


# 所感、注意点

まず、今回の自作音楽ストリーミングサービスを構築したことで、オープンソースで完成度の高い音楽ストリーミングアプリがあること、個人利用でHTTPSを使った通信を実現する仕組みがあることなどが学びとなった。
また、自作音楽ストリーミングサーバを作る点で意識しておかなければならない点を次の通り考えている。
まず、インターネット上で不特定多数に対し自身の音楽ファイルを使った音楽ストリーミングサービスをアクセスフリーで公開した場合、間違いなく著作権絡みの法律に違反するため必ずEmbyのユーザ作成とパスワード設定を行い、自分自身しかストリーミングサービスを利用できないようにしておく必要がある。また、セキュリティ上Embyの管理者アカウントのパスワードも必ず複雑なものに変更しておく。
そして、ストリーミング動作についてだが、曲を再生する時には保存した音楽ファイルのファイルサイズと同等の通信容量を消費する（ブラウザの開発ツールで確認した）ので、出先のモバイル回線から利用する際を考慮すると、配置する音楽ファイルのフォーマットは数十MBするようなFLAC等の可逆圧縮方式のファイルだと少し厳しく、数MBで済むAACやMP3が無難と思われる（もしかしたら動的にダウンコンバートする設定があるかもしれないが、まだ深堀りできていない）。