Nervesのネットワーク設定を`mix firmware`する時に指定できるようにした

Nervesを複数台準備していろいろ遊んでみようとした時に「あれ？これってIPアドレス以外同じやん。Nerves毎に`mix nerves.new`するのって管理面倒やん。」って思ったので`mix firmware`した時にIPアドレスなどを引数として渡してIPアドレスを指定できるようにしてみました。

これは「[Nervesのnode_nameとnode_hostとcookieを設定した](https://qiita.com/nishiuchikazuma/items/1ae314c47b635d9a1ce8)」の続編になります。


# 環境

## ハードウェア
- Mac OSX
- NervesをRaspberryPi 3で稼働


## ネットワーク
- Mac OSX
  - 有線LAN→メンテナンス用、固定IPアドレス（192.168.5.100/24）

- ラズパイ
  - 有線LAN（eth0）→メンテナンス用、固定IPアドレス（192.168.5/24の範囲）
  - 無線LAN（wlan0）→インターネット接続用、DHCP（192.168.46/24から自動割当）
  - 192.168.5.240(default)は後述

![ネットワーク構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51020/95e29daf-01e9-ac3b-3e5e-3d4448db4f19.png)


# 各ファイルへの設定（これまでのまとめ含む）

前提として`mix nerves.new`しているプロジェクトがあることとします。

## mix.exs
`mix.exs`に必要なライブラリを追加します。
`:nerves_firmware_ssh`は今回は不要ですが、まとめとして記載しておきます。

```elixir:mix.exs
defp deps do
      ...
      # 追加
      {:nerves_network, "~> 0.5", targets: @all_targets},
      {:nerves_firmware_ssh, "~> 0.3", targets: @all_targets},
      ...
```


## config/target.exs
ネットワークまわりの設定が多くなりすぎたので別ファイル`network.exs`に分けることにします。

```elixir:config/target.exs
import_config("network.exs")
```


## config/network.exs
`$ mix firmware <IPアドレス> <サブネットマスク> <デフォルトゲートウェイ>`とすると各設定が入ったNervesイメージを作成するように`config/network.exs`を作成します。

直前の引数が指定されていることが必須で、引数が設定されていない場合のデフォルトの値は以下のようにしました。

- IPアドレス：192.168.5.240
- サブネットマスク：255.255.255.0
- デフォルトゲートウェイ：（設定なし）




```bash:bash
$ mix firmware <IPアドレス> <サブネットマスク> <デフォルトゲートウェイ>

（例）
$ mix firmware "192.168.5.55" "255.255.255.0" "192.168.5.1"
$ mix firmware "192.168.5.150" "255.255.255.128"
$ mix firmware "192.168.5.200"
$ mix firmware    ←この場合はデフォルト設定
```

```elixir:config/network.exs
#
# ライブラリ読み込み
#
import Config



#
# 変数の設定
#
arg_2nd         = 1
arg_3rd         = 2
arg_4th         = 3
arg_ip          = System.argv() |> Enum.at(arg_2nd)
arg_subnet      = System.argv() |> Enum.at(arg_3rd)
arg_gateway     = System.argv() |> Enum.at(arg_4th)
default_ip      = "192.168.5.240"
default_subnet  = "255.255.255.0"
default_gateway = ""

wired_ip = case arg_ip do
  nil -> default_ip
  _   -> arg_ip
end

wired_subnet = case arg_subnet do
  nil -> default_subnet
  _   -> arg_subnet
end

wired_gateway = case arg_gateway do
  nil -> default_gateway
  _   -> arg_gateway
end



#
# ネットワーク設定
#
settings = [
  networks: [
    [ssid: "SSIDの設定", psk: "PSKの設定", key_mgmt: :"WPA-PSK", priority: 0,
      ipv4_address_method: :dhcp,
      nameservers: ["8.8.8.8", "8.8.4.4"]
    ]
  ]
]

config :nerves_network, default: [
  wlan0: settings,
  eth0: [
    ipv4_address_method: :static,
    ipv4_address: wired_ip,
    ipv4_subnet_mask: wired_subnet,
    ipv4_gateway: wired_gateway
  ]
]

```

## 参考
`System.argv()`すると`firmware`を第一引数としてリストが生成されるので`Enum.at/2`で場所を指定して利用しています。

```elixir:iex
（例）
$ mix firmware "192.168.5.55" "255.255.255.128" "192.168.5.1"

iex> System.argv()
["firmware", "192.168.5.55", "255.255.255.128", "192.168.5.1"]
```



# 確認
引数をすべて指定している場合とIPアドレスのみ指定している場合で確認します。

## すべての引数がある場合
```bash:bash
$ mix deps.get
$ mix firmware "192.168.5.55" "255.255.255.128" "192.168.5.1"
$ mix firmware.burn

$ ssh 192.168.5.150
iex> Nerves.NetworkInterface.settings("eth0")
{:ok,
 %{
   ipv4_address: "192.168.5.55",
   ipv4_broadcast: "192.168.5.127",
   ipv4_gateway: "192.168.5.1",
   ipv4_subnet_mask: "255.255.255.128",
   mac_address: "b8:27:eb:96:da:51"
 }}
```

## IPアドレスのみ指定している場合
IPアドレスが設定されているので`upload.sh`を使ってNervesイメージを更新します。
サブネットマスクとデフォルトゲートウェイは指定していなのでデフォルトの値が設定されます。

```bash:bash
$ mix deps.get
$ mix firmware "192.168.5.55"
$ ./upload.sh 192.168.5.55

$ ssh 192.168.5.55
iex> Nerves.NetworkInterface.settings("eth0")
{:ok,
 %{
   ipv4_address: "192.168.5.55",
   ipv4_broadcast: "192.168.5.255",
   ipv4_gateway: "",
   ipv4_subnet_mask: "255.255.255.0",
   mac_address: "b8:27:eb:96:da:51"
 }}
```

# まとめ
`mix firmware`の際、引数を与えてNervesのイメージを変更できました。

これまではNervesプロジェクトの設定ファイルを確認すればIPアドレスが判断できてました。が、今回のように設定ファイルにIPアドレスを記載しない方法にすると、どのNervesがどのIPアドレスを設定しているかが分からなくなる副作用があることがわかりました＼(^o^)／