[ Raspberry Pi ] アクセスポイント化とWebサーバーを自動起動する方法

# はじめに
この記事では, `Raspberry Pi`を使って, 簡単なアクセスポイントとWebサーバーを構築していきます.

`hostapd`・`dnsmasq`・`dhcpcd`を使用して`Raspberry Pi`をアクセスポイント化し, `Flask`アプリケーションを自動起動する方法を解説します.

また, 今回はRaspberry Pi OS Liteを使用し, ディスプレイやキーボードを接続せず, すべての作業をSSHを使ってリモートで行います.

# 使用機材
- Raspberry Pi 4-B (OS: Lite 64bit)
- Mac Book Air
- LANケーブル

# 環境構築
環境構築をしていきます. 

## 準備

:::note warn
今回は, `Mac`を使って`Raspberry Pi`に有線接続して`SSH`しているので, 不要な方はこの工程を省いて大丈夫です.
:::

まず, Macと`Raspberry Pi`を有線LANケーブルで接続します.

システム設定 -> ネットワークから該当するインターフェースを選び, 以下のように静的IPアドレスを指定します.

今回は, `192.168.1.1`として割り当てました.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3740411/1c87ab68-ebc0-ae48-6d72-149339041aaf.png)

SSHをします.
```
ssh pi@raspberrypi.local
```
これでモニターとキーボードを使わずに, 作業することが出来ます.

## 必要なパッケージのインストール
基本的に`/home/pi`上で作業します.
以下のコマンドを実行して必要なパッケージをインストールします.
```
sudo apt-get update
```
```
sudo apt-get -y install hostapd dnsmasq dhcpcd5
```
```
sudo apt-get -y install python3-pip
```
vimをインストールしますが, エディタはお好みのものをお使いください. 
vimを使わない方は, これ以降vimをお使いのエディタに読み替えてください.
```
sudo apt-get -y install vim
```

## Pythonの仮想環境構築とFlaskのインストール
仮想環境はvenvを使用していきます.
```
python -m venv .venv
```
以下で仮想環境有効化.
```
source /home/pi/.venv/bin/activate 
```
Flaskをインストールします.
```
pip install Flask
```

## NetworkManagerの無効化
アクセスポイント化したときに競合を避けるため, `NetworkManager`を無効化します.
```
sudo systemctl disable NetworkManager
```

# Pythonスクリプト
今回は, Flaskアプリケーションのコードは`app.py`に書きます. 

また, アクセスポイントを立ち上げ, `app.py`を呼び出すコードは`main.py`に書いていきます.

## app.py (Flaskアプリケーション)
ここでは, Flaskアプリケーションを最小構成にします.
h1タグの`Hello, World!`を返します.

main.pyでこのファイルを`import`するので, `app.run()`を`start`関数内に書きます. 

`host="0.0.0.0"`は, Flaskが任意のIPアドレスでリクエストを受け付けます. ポートは, Webアプリケーションのテストに使われる`8080`を使用しました. 

:::note warn
使用するポートの競合に注意してください.
:::

```python
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "<h1>Hello, World!</h1>"

def start():
    app.run(host="0.0.0.0", port=8080)
```

## main.py (アクセスポイント)
`subprocess`というコマンドを`import`します.
また, 先ほど作った`app.py`も同じく`import`します.
\
`ssid`の値は, `RaspberryPiAccessPoint`にしました. また, パスワードは`1234ABCD`としました. これらは任意のもので大丈夫です.
\
ap_configでは, Wi-Fiインターフェースとして`wlan0`を指定しています. 

アクセスポイント用に使用するLinuxのWi-Fiドライバ`nl80211`を指定, `hw_mode=a`で5GHz帯をWi-Fiの動作モードとして指定, Wi-Fiのチャンネルは, 5GHzの36チャンネルを指定しています.
```python
import subprocess
import app

def start_access_point():
    ssid = "RaspberryPiAccessPoint"
    password = "1234ABCD"
    ap_config = f"""interface=wlan0
driver=nl80211
ssid={ssid}
hw_mode=a
channel=36
ieee80211n=1
ieee80211ac=1
wmm_enabled=0
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase={password}
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
"""
```
先程の設定を`hostapd.conf`に書き込んでいきます.

3つ目の`with open`の箇所では, `hostapd`が使用する設定ファイルを指定しています.

```Python
def start_access_point():
    (省略)

    with open("/etc/hostapd/hostapd.conf", "w") as f:
        f.write(ap_config)

    hostapd_default = "/etc/default/hostapd"
    with open(hostapd_default, "r") as f:
        content = f.read()
    with open(hostapd_default, "w") as f:
        content = content.replace("#DAEMON_CONF=\"\"", f'DAEMON_CONF="/etc/hostapd/hostapd.conf"')
        f.write(content)
```
dhcpcd(DHCP Client Daemon)は, ネットワークインターフェースにIPアドレスを割り当てる役割があります.

:::note warn
今回は, `Mac`を使って`Raspberry Pi`に有線接続して`SSH`しているので, 不要な方は`eth0`のコードは省いて大丈夫です.
:::
`eth0`と`wlan0`に静的IPアドレスを設定し, `Raspberry Pi`をアクセスポイントとして動作させる準備を行っていきます.

`eth0`にはMac側から手動でIPアドレスを`192.168.1.1`に指定したのでそれに合わせています. サブネットマスクは`/24`です. デフォルトゲートウェイとして`192.168.1.1`を指定し, DNSサーバーを`192.168.1.1`に指定しています.
\
`wlan0`には, `192.168.0.50`を静的アドレスとして割り当ててます(サブネットマスクは`/24`). `nohook wpa_supplicant`は, `WPA Supplicant`を無効化しています.


```python
def start_access_point():
    (省略)
    
    eth0_config = """\ninterface eth0
static ip_address=192.168.1.1/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1
"""

    wlan0_config = """\ninterface wlan0
static ip_address=192.168.0.50/24
nohook wpa_supplicant
"""

    with open("/etc/dhcpcd.conf", "w") as f:
    	f.write(eth0_config)
	    f.write(wlan0_config)

```
`dnsmasq`は, DNSサーバーの役割と自動的にIPアドレスを割り当てる役割があります.
\
`192.168.0.50`をDNSリクエストの`listen-address`として指定します.

`server=8.8.8.8`はGoogleのパブリックDNSを上流のDNSサーバーとして指定します.


`dhcp-range=192.168.0.50,192.168.0.70,12h`では, DHCPで割り当てるIPアドレスの範囲を指定します. 
`192.168.0.50`から`192.168.0.70`の間で, リース期間は12時間です.
```python
def start_access_point():
    (省略)
    
    dnsmasq_config = """
interface=wlan0
listen-address=192.168.0.50
server=8.8.8.8
domain-needed
bogus-priv
dhcp-range=192.168.0.50,192.168.0.70,12h
"""

    with open("/etc/dnsmasq.conf", "w") as f:
        f.write(dnsmasq_config)
```
`hostapd`, `dnsmasq`, `dhcpcd`を起動していきます.
最後に, `main`でアクセスポイントの関数を呼び出した後, Flaskアプリケーションを起動しています. 
```python
def start_access_point():
    (省略)
    subprocess.call(["sudo", "systemctl", "unmask", "hostapd"])
    subprocess.call(["sudo", "systemctl", "unmask", "dhcpcd"])
    subprocess.call(["sudo", "systemctl", "enable", "hostapd"])
    subprocess.call(["sudo", "systemctl", "enable", "dhcpcd"])
    subprocess.call(["sudo", "systemctl", "start", "hostapd"])
    subprocess.call(["sudo", "systemctl", "enable", "dnsmasq"])
    subprocess.call(["sudo", "systemctl", "restart", "dhcpcd"])
    subprocess.call(["sudo", "systemctl", "restart", "dnsmasq"])
    subprocess.call(["sudo", "systemctl", "restart", "hostapd"])

if __name__ == "__main__":
    start_access_point()
    app.start()
```
これで, アクセスポイントを立てる -> Flaskアプリケーションを起動するというところまで完成しました.

全コードは[GitHub](https://github.com/kanade-o/raspberry-pi-ap-simple-webserver)にあげています.

# 自動起動設定(systemd)
systemdを使って, 自動起動させていきます.
以下のようにserviceファイルを作ります.
```
sudo vim /etc/systemd/system/web_server.service
```
以下の内容を記入していきます. 
ポイントは.venvのPythonを起動しているところです.
```shell
[Unit]
Description=start access point and start web server
After=network.target

[Service]
ExecStart=/home/pi/.venv/bin/python /home/pi/main.py
WorkingDirectory=/home/pi

[Install]
WantedBy=multi-user.target
```
最後に, 自動起動を有効化します.
```
sudo systemctl enable web_server
```
# 動かしてみる
それでは動かしてみましょう.
自動起動するか確かめたいので, 再起動します.
```
sudo reboot
```
アクセスポイントが見えたら, 接続します.
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3740411/b3768ec1-0002-d7e0-f6d6-f071b0e6f42b.jpeg" width=50%>

そして, http://192.168.0.50:8080 に入り, 以下のような画面になれば成功です!

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3740411/d65ec360-5fc6-ed20-cf45-d6199591c86e.jpeg" width=50%>


# おわりに
今回は, あるプロジェクトの中でこの記事の内容を実装する機会があったので, 復習と整理のためにこの記事を書いてみました.

Raspberry Piやネットワークについて理解が深まりました.


# 参考文献
https://qiita.com/gamelike319/items/eb8d8cb93bc562039ffa

https://dev.classmethod.jp/articles/raspberry-pi-4-develop-with-mac-using-usb-c-cable-2/

https://www.mikan-tech.net/entry/raspi-wifi-ap

https://note.com/tango9512357/n/na14c784fdbfd

https://monorepi.jp/archives/1793