ElixirでIoT#4.1.2：[使い方篇] Docker(とVS Code)だけ！でNerves開発環境を整備する

この記事は[ fukuoka.ex Elixir／Phoenix Advent Calendar 2020 ](https://qiita.com/advent-calendar/2020/fukuokaex)の4日目です．
3日目は，@torifukukaiouさんの[「ElixirでAtCoderのABC123を解いてみる！」](https://qiita.com/torifukukaiou/items/75d5db21d98fdf4459e2)でした．awesome!!:tada::tada::tada:

# はじめに

どうも最近は開発環境厨みが増してきている気がします．
[ElixirでIoT！](https://qiita.com/takasehideki/items/8f43f1853ce88cbbe82e)の連載記事も久々な気がするんですが，今回も開発環境な記事です^^;

これまでの実績はこんな感じです．

- [ElixirでIoT#4.1：Nerves開発環境の準備](https://qiita.com/takasehideki/items/88dda57758051d45fcf9)
- [ElixirでIoT#4.1.1：WSL 2でNerves開発環境を整備する](https://qiita.com/takasehideki/items/b8ea8b3455c70398178a)
- [ElixirでIoT#3.1：ESP32やSTM32でElixirが動く！AtomVMという選択肢](https://qiita.com/takasehideki/items/847d3d8f9d255e01ce9c)

なんか新しいものを導入するたびにホスト環境はいろいろ汚れるわけですが，それを解消するために最近はDockerにハマりつつあります．
@matsujirushi さんがこんな記事を公開していました！

- [WindowsでNervesやるならWSL2+Docker+VSCode](https://qiita.com/matsujirushi/items/f0bde41400e45e309df2)

こんなcoolなsolution，もうちょい綺麗にできそう！ってことで， @matsujirushi さんにリポジトリの権限をいただきつつ，**Docker（とVS Code）だけ**で素敵な開発環境を整えてみました！！

この記事では整備した環境について，ユーザ目線での使い方を主に紹介します．
中身の解説はまた機会を改めて^^;

# つくったもの

- Dockerfile: https://github.com/NervesJP/docker-nerves
- Docker Hub (pre-built image): https://hub.docker.com/r/nervesjp/nerves
- VS Code dev-container: https://github.com/NervesJP/nerves-devcontainer


## なにが嬉しいの？いつ使うの？

- Dockerを使えば，数ステップだけでNervesの開発環境が整います．[ElixirでIoT#4.1](https://qiita.com/takasehideki/items/88dda57758051d45fcf9)で紹介したみたいに，Nervesの開発環境の準備って，いろいろライブラリを入れたり，いろいろErlangインストールにハマったり，いろいろ大変なんですよね,,, そんなシチュエーションが気楽に回避できます．
- 特に入門者にとっては嬉しいですし，知り合いにすぐ試してもらいたい！場合にも紹介しやくなるかと思います．
- ハンズオンの開催を計画する時にすごく頭を悩ませるのが，参加者の環境を揃えることです．DockerfileとかVS Codeの各種設定ファイルを調整すれば，これもお手軽に解消できます！！
    - |> [［PR］参考例［広告］](https://algyan.connpass.com/event/197306/)
    - |> [この例の導入方法［ステマ］](https://qiita.com/takasehideki/items/79d4ba3f95b1463105f8)
- すでにNervesを導入済みの方でも，最新のバージョンや機能を味見で試してみたいことは多いです．そんなときにDockerを使えば，ホストの環境は汚さずにお手軽に最新版を試すことができます．asdfでErlang/Elixirのバージョンを切り替えて `mix archive.install hex nerves_bootstrap` のバージョンも,,, って煩わしさも無くなります．
- [Docker Hub](https://hub.docker.com/r/nervesjp/nerves)にビルド済みのイメージを公開しています．これを使えば，さらに効率よくNerves開発環境が手に入ります（ただし最新版じゃないことがありますので，ご注意ください．全てはメンテナの @takasehideki の頑張り次第です^^;

## ツラいことは？？

- Dockerを動かすので，CPU／メモリの負荷がそれなりに掛かります．あとイメージ展開のためのディスク容量も必要です．
- `mix burn` でNervesファームウェアをmicroSDカードに書き込む作業ができません,,, ホスト上で `fwup` を実行する必要があります:fire::fire::fire:

# 導入手順

インストールが必要なものは下記です．

- [Docker Desktop](https://www.docker.com/products/docker-desktop)
- VS Codeを使用する場合：[Visual Studio Code ](https://code.visualstudio.com/download)と拡張機能[ Remote - Containers ](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
- fwup（後述）

DockerとVS Codeの詳しい導入方法は[「ALGYAN x Seeed x NervesJPハンズオン！に向けた開発環境の準備方法」](https://qiita.com/takasehideki/items/79d4ba3f95b1463105f8)の記事を参照してください．

# Docker単体で動かす

## [Docker Hubリポジトリから](https://hub.docker.com/r/nervesjp/nerves)

ビルド済みのDockerイメージを使用します．`pull`して`run`するだけです．

```shell-session
$ docker pull nervesjp/nerves
$ docker run -it -w /workspace nervesjp/nerves
root@6e304327bd2e:/workspace# 
```

## [GitHubリポジトリから](https://github.com/NervesJP/docker-nerves)

GitHubで公開しているDockerfileを使ってローカルでビルドします．自分好みにDockerfileをカスタマイズすることもできます．

<a href="https://github.com/NervesJP/docker-nerves"><img src="https://github-link-card.s3.ap-northeast-1.amazonaws.com/NervesJP/docker-nerves.png" width="460px"></a>


```shell-session
$ git clone https://github.com/NervesJP/docker-nerves
$ cd docker-nerves

$ docker build -t docker-nerves .
$ docker run -it -w /workspace docker-nerves 
root@9bc88d0fc7b8:/workspace# pwd
/workspace
root@9bc88d0fc7b8:/workspace# echo ${HOME}
/root
root@9bc88d0fc7b8:/workspace# ls ~/.mix/*
/root/.mix/rebar  /root/.mix/rebar3

/root/.mix/archives:
hex-0.20.6  nerves_bootstrap-1.10.0
```

## Docker単体動作でのTips

### ファイルシステムのマウント

Dockerはイメージの再構築時／実行時に，そのファイルシステムが消えてしまいます．このため，Nervesプロジェクトのファイルを保持するためにホストにボリュームをマウントしておくのが便利です．
`-v $ {PWD}：/ workspace` によって，ホスト上の現在のディレクトリをDockerイメージにマウントできます．


```shell-session
$ docker run -it -w /workspace -v ${PWD}:/workspace docker-nerves 
```

Elixir/Nervesに関連する設定ファイルをマウントしておくのも効率的です． `~/.hex`， `~/.nerves`， `.ssh/` があります．
以下は，ホストとイメージ間で設定ファイルを共有する例です．

```shell-session
$ docker run -it -w /workspace -v ${PWD}:/workspace \\
  -v ~/.hex:/root/.hex -v ~/.nerves:/root/.nerves -v ~/.ssh:/root/.ssh \\
  docker-nerves 
```

### 環境変数の設定

Dockerコンテナの立ち上げ時にシェルの環境変数を設定することができます．

Nerves開発時に必須の環境変数として `${MIX_TARGET}` があります．
開発対象とするNervesの[target](https://hexdocs.pm/nerves/targets.html)がすでに決まっていて固定されているなら，この変数を設定したほうが楽になります．
次の例では`rpi3`に設定してます．

```shell-session
$ docker run -it -w /workspace -e MIX_TARGET=rpi3 docker-nerves 
root@deda9932d7e3:/workspace# echo $MIX_TARGET
rpi3
```

複数の環境変数を同時に設定したい場合は `--env-file` を使うと良いでしょう．
例えば，vintage_net_wifiのWiFi接続設定を環境変数で与える方法があります．
下記のように `env.list` を作成してオプションで与えるだけです．

```shell-session
$ cat env.list 
MIX_TARGET=rpi3
WIFI_SSID=xxxxxxxx
WIFI_PSK=yyyyyyyy
$ docker run -it -w /workspace --env-file env.list docker-nerves 
root@cf815278594a:/workspace# echo $MIX_TARGET
rpi3
root@cf815278594a:/workspace# echo $WIFI_SSID
xxxxxxxx
root@cf815278594a:/workspace# echo $WIFI_PSK
yyyyyyyy
```

環境変数を用いたVintageNetのWiFiの設定方法は，次の記事をご参照ください（どちらも両記事！！:tada:

- [[Elixir/Nerves] Get started with LED Blinking on Raspberry Pi - DEV](https://dev.to/mnishiguchi/elixir-nerves-get-started-with-led-blinking-on-raspberry-pi-2l1i)
- [ウェブチカでElixir/Nervesに入門する（2020年12月版） - Qiita](https://qiita.com/kentaro/items/e8df79aa93b9fe9a567e#wi-fi%E7%B5%8C%E7%94%B1%E3%81%A7%E3%83%95%E3%82%A1%E3%83%BC%E3%83%A0%E3%82%A6%E3%82%A7%E3%82%A2%E3%82%92%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B)


# VS Codeでより便利に！

みんな大好きVisual Studio Codeを使うと，さらに快適な開発環境が手に入ります．
通称dev-containerという拡張機能 Remote - Containers を利用します．
詳しくは下記の記事などをご参照ください〜

- [公式の資料：Developing inside a Container using Visual Studio Code Remote Development](https://code.visualstudio.com/docs/remote/containers)
- [Qiita記事：VSCode Remote Containerが良い](https://qiita.com/d0ne1s/items/d2649801c6f804019db7)

## 使用方法

### 1. リポジトリをclone

```shell-session
git clone https://github.com/NervesJP/nerves-devcontainer
```

Gitに慣れていなければ，下記から直接ダウンロードもできます．
https://github.com/NervesJP/nerves-devcontainer/archive/main.zip

<a href="https://github.com/NervesJP/nerves-devcontainer"><img src="https://github-link-card.s3.ap-northeast-1.amazonaws.com/NervesJP/nerves-devcontainer.png" width="460px"></a>


### 2. VS Codeをdev-container機能で開く

VS Codeを起動して，クイックアクションのステータスバー項目（左下の緑の<font color="green"><b> >< </b></font>となっているところ）か`F1`をクリックします．
コマンドパレットが開きますので，**Remote-Containers: Open Folder in Container...**を選択し，手順1でcloneしてきたディレクトリを選択します．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240349/f0f2c410-dd31-dce4-e266-df293d8541b1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240349/57be67e2-0eba-36a4-ecc5-bd1ceea3b6f9.png)

### 3. コーヒーを淹れる:coffee:

Dockerイメージのダウンロードと展開がよしなに始まります．PCスペックとネットワーク環境によっては10分くらい掛かるかもしれません．コーヒーでも呑みながら，落ち着いてお待ちください

うまくいかない？場合は，Docker Desktopのアプリが起動していないことが考えられます．
Windowsだと，デスクトップ右下のタスクトレイにこんな感じのクジラのアイコンが表示されているか確認してください（Macだとメニューバーにいてます）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240349/a3100d08-4068-e25f-9ef4-f6ecbc0c0a99.png)

ちゃんと進んでいるか気になる方は， 右下の青字の "<font color="blue">Starting Dev Container (show log)</font>" をクリックすると，進捗のログが表示できます．

ということで，こんな感じに出来上がりっ♪
左下の緑色なところが
<b><font color="green"> >< Dev Container: Nerves</font></b>
な表示になっていることを確認してください．
（画像は途中で "<font color="blue">Starting Dev Container (show log)</font>" をクリックして進捗を表示した場合のものです）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240349/101b2a97-eabd-17a3-c878-786ce89bea24.png)

メニューの [ターミナル] > [新しいターミナル]（ショートカットキーは `Ctrl+Shift+｀`）でターミナルを開くと， Dockerイメージ内でNerves開発環境の整ったターミナルを良い感じに利用できます．
（起動直後は［新しいターミナル］が選択できないことがあるようです．その場合は左側［エクスプローラー］で右クリックして［統合ターミナルで開く］を選択してみてください）
  
```shell-session
root@ebc5e1a19ae3:/workspaces/nerves-devcontainer# ls 
LICENSE.txt  README.md
root@ebc5e1a19ae3:/workspaces/nerves-devcontainer# ls ~/.mix/*
/root/.mix/rebar  /root/.mix/rebar3

/root/.mix/archives:
hex-0.20.6  nerves_bootstrap-1.10.0
root@ebc5e1a19ae3:/workspaces/nerves-devcontainer# elixir --version 
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Elixir 1.11.2 (compiled with Erlang/OTP 23)
root@ebc5e1a19ae3:/workspaces/nerves-devcontainer# iex 
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 
```

### 4. SSH鍵を作成する

Nervesのファームウェア開発とネットワーク接続には，SSH鍵交換方式が使われます．
下記のように，いちばん代表的であるRSA鍵を作成しておきます．

```shell-session
ssh-keygen -t rsa -N "" -f .ssh/id_rsa
```

## Tips a.k.a コダワリどころ

### ローカルでのイメージの手動ビルド

GitHubで公開しているVS Codeのディレクトリは，[Docker Hubでビルド済みのイメージ](https://hub.docker.com/r/nervesjp/nerves)のを使用しています．
手元でカスタマイズしてビルドしたい場合は，もともとの[Dockerfile](https://github.com/NervesJP/docker-nerves/blob/main/Dockerfile)を `./.devcontainer` に配置して， `./.devcontainer/devcontainer.json` の `	//"dockerFile": "Dockerfile",` の行のコメントを解除してください．

```json:.devcontainer/devcontainer.json
{
	"name": "Nerves",
	// Pull Docker image from DockerHub  https://hub.docker.com/r/nervesjp/nerves
	//"image": "nervesjp/nerves:0.1.1",
	// NOTE: original Dockerfile is maintained at https://github.com/NervesJP/docker-nerves/blob/main/Dockerfile
	// You can also build Docker image locally by locating above file to here and uncomment next line 
	"dockerFile": "Dockerfile",

〜〜（省略）〜〜
```

### Nerves設定に関するファイルのマウント

先ほどのTIPSと同じく，イメージの再構築時／実行時に，そのファイルシステムが消えてしまいます．
VS Code／GitHubでは `docker run` 時のオプションも統一できるので，これらのファイルが含まれるディレクトリをカレントに保持するようにしました．
（これらはどこにマウントするのがいいのか，割りと悩ましかったです．`${HOME}` のほうがええんちゃうかという気もします．ご意見いただけたら嬉しいです）

1つめは，Dockerコンテナ内の `~/.hex` にマウントされる `${PWD}/.hex` です．ElixirのHexパッケージが保持されます．

2つめは，Dockerコンテナ内の `~/.nerves` にマウントされる `${PWD}/.nerves` です．このディレクトリでは，Nervesのツールチェーンとnerves_system_brのアーカイブが保持されます．

Dockerコンテナの再起動のたびに `mix deps.get` でこのアーカイブのダウンロード／展開は面倒ですし時間が掛かります．なので，このディレクトリをマウントするようにしています．
ホストとコンテナのファイルシステム間のバインディングはパフォーマンスが悪いため，最初の `mixdeps.get` の実行時には多少の時間が掛かります．ですが，Docker起動のたびにダウンロード／展開に待たされるよりはマシじゃね？と判断しました．

最後に `${PWD}/.ssh` です．
ステップ4で作成した `id_rsa` と `id_rsa.pub` のSSHキーペアが含まれます．これらは，Nervesファームウェアの構築とNervesデバイスへのSSH接続のために使用されます．

さらに，VS Code拡張機能の自動設定で，Nervesのプロジェクトファイルも現在のディレクトリに保持されます．

### 環境変数の設定

これまたVS Code拡張機能の便利なところで， `./.devcontainer/devcontainer.json` にてコンテナ内のシェル環境変数を設定することができます．

今回はこんな感じで設定してあります．お好みに合わせて編集してからご利用ください．

```json:.devcontainer/devcontainer.json
(省略)
	// This section sets environment variables for Nerves development
	"remoteEnv": {
		"MIX_TARGET": "rpi3",
		"WIFI_SSID": "xxxxxxxx",
		"WIFI_PSK": "yyyyyyyy"
	}
(省略)
```

# 制約事項,,, mix burnできないorz

すっげー便利やん！と思うのですが，冒頭にも挙げている欠点がひとつだけあります．
Docker環境からはmicroSDカードへのアクセスができません,,, このため，せっかくDocker内でビルドしたNervesファームウェアを，`mix burn`でそのままmicroSDカードに書き込めません．

代案として，Nervesファームウェアの書込みのためのユーティリティである `fwup` をホストにインストールして実行します．
https://github.com/fhunleth/fwup

<a href="https://github.com/fhunleth/fwup"><img src="https://github-link-card.s3.ap-northeast-1.amazonaws.com/fhunleth/fwup.png" width="460px"></a>


## fwupのインストール

**Windowsの場合**

[Chocolatery](https://chocolatey.org/)を使われている方は，下記でインストールできます．

```shell-session
choco install fwup
```

そうでない方は，下記からバイナリを直接ダウンロードして，ダウンロード先に[Path環境変数を設定](https://www.atmarkit.co.jp/ait/articles/1805/11/news035.html)してください．
https://github.com/fhunleth/fwup/releases/download/v1.8.3/fwup.exe

上記URLの`v1.8.3`のバージョン番号は，最新リリースに合わせて変更してください．


**macOSの場合**

[brew](https://brew.sh/index_ja)でインストールできます．

```shell-session
brew install fwup
```

**Linuxの場合**

プラットフォームに合わせて，パッケージのダウンロードとインストールを行ってください．
https://github.com/fhunleth/fwup#installing

例えばDebian/Ubuntuならこんな感じです．

```shell-session
$ wget https://github.com/fhunleth/fwup/releases/download/v1.8.3/fwup_1.8.3_amd64.deb
$ sudo dpkg -i fwup_1.8.3_amd64.deb
```

上記URLの`v1.8.3`のバージョン番号は，最新リリースに合わせて変更してください．


## fwupによるファームウェアの書込み

インストールが完了したら，ターミナルを開いてNervesのプロジェクトのディレクトリに移動して，下記のとおり実行してください．
Windowsの方は，PowerShellを**「管理者として実行する」**で開く必要があります．

```shell-session
$ cd <your_nerves_project_dir>
$ fwup _build/${MIX_TARGET}_dev/nerves/images/<project_name>.fw
```

Dockerだから仕方ない話しではあるのですが，なんかうまいことできたら嬉しいところです．
例えばmicroSDの `/dev/diskX` を書き込み可能でアクセスできたり，ホストにシェルプロセスの立ち上げをそこで実行したり,,, （WSL 2ではそんな機能があるらしい？？

良い方法をご存じの方がいましたら，[ここのissue#1](https://github.com/NervesJP/docker-nerves/issues/1)かコメント欄で教えてください〜


# おわりに

この記事の初稿は，実は英語で書きました．
よっしゃElixir Forumに投稿してやんぜ！世界のみんなを楽にしてやんぜ！Advent Calendarも英語記事を登録やっ！！
って息巻いていたんですが，どうもElixir ForumはModeratorsの審査承認作業があるらしく,,,
敢えなく日本語で書き直す，そんな不毛な作業の果てでした^^;

↓ この1時間後,,, 無事にapprovedされて公開されました！！ :grin: 

- [Nerves development environment with Docker (and VS Code) - Nerves Forum / Chat / Discussions - Elixir Programming Language Forum](https://elixirforum.com/t/nerves-development-environment-with-docker-and-vs-code/35973)

---

[ fukuoka.ex Elixir／Phoenix Advent Calendar 2020 ](https://qiita.com/advent-calendar/2020/fukuokaex)の5日目は @hisaway さんによる[「[Elixir]匿名関数をパターンマッチする」](https://qiita.com/hisaway/items/db0d6cefc5ac46a73ec4)です．ASTの世界をチラ見しましょっ:eyes: