milk-V Duo（64MB）を買ってみた

*買ったのは1年ぐらい前で未開封のまま放置していたのですが…。*

人生ハイド（隠れて目立たないように生きている意）担当の @nakochi です。

アドベントカレンダーをノリで2枠も取ったものの、なにを書くか深く考えていなかったので、前日になって一年ほど前に購入した [milk-V Duo](https://milkv.io/ja/duo) を引っ張り出してきました。

<img width="320" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3592296/5b27edee-a243-badb-c1f4-56fcdcb153a1.jpeg" alt="ミルクファイブの実機画像">

# milk-V Duo （64MB） とは
超格安シングルボードコンピュータの一種です。本体（画像では白いボードの**上に載ってる茶色っぽいボード**）であればみんな大好き[秋月電子通商さんにて1,420円ほど](https://akizukidenshi.com/catalog/g/g118374/)で購入できてしまいます。
この安さだけでもすごいのですが、もう一つの注目ポイントとしてCPUアーキテクチャが***今最もアツいRISC-V***という点があります。

他、スペックとしては以下のようになっています: 
- SoC: CVITEK CV1800B
- CPU1: RISC-V C906@1GHz for Linux
- CPU2: RISC-V C906@700MHz for FreeRTOS
- RAM: 64MB（！）

他、256MBモデルもあり、こちらの場合は文字通りRAMが256MBに増えるほか、なんとARMコア（Cortex-A53@1GHz）まで内蔵されているようです（ただしRISC-Vのメインコアとは排他）。わりと意味がわからなく、発表当初は界隈が若干ざわついたりしていました。

# とりあえず起動してみた
公式イメージもあるのですが、今回は[フォーラムに投稿されていたDebianイメージ](https://community.milkv.io/t/milkv-duo-duo-debian-full-7z-519m-download/862)で起動してみました。ソースコードもしっかり公開されているとはいえ、試される際は十分注意してご利用ください。当環境ではメインのネットワークとは完全に切り離した（大元の回線契約ごと違う）環境にて遊んでおります。

```shell
$ ssh root@10.255.255.7
> root@10.255.255.7 password: riscv

> The programs included with the Debian GNU/Linux system are free software;
> the exact distribution terms for each program are described in the
> individual files in /usr/share/doc/\*/copyright.

> Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
> permitted by applicable law.

$ root@milkv-duo:~# apt update
> Get:1 https://mirror.sjtu.edu.cn/debian-ports sid InRelease [74.2 kB]
> Get:2 https://mirror.sjtu.edu.cn/debian-ports unstable InRelease [74.2 kB]
> Get:3 https://mirror.sjtu.edu.cn/debian-ports unreleased InRelease [50.0 kB]
> Get:4 https://mirror.sjtu.edu.cn/debian-ports experimental InRelease [74.3 kB]
> Fetched 273 kB in 8s (32.6 kB/s)
> Reading package lists... Done
> Building dependency tree... Done
> Reading state information... Done
> All packages are up to date.

$ root@milkv-duo:~# apt install nginx
> Reading package lists... Done
> Building dependency tree... Done
> Reading state information... Done
> E: Unable to locate package nginx

$ root@milkv-duo:~# free -h
>       total  used   free   shared  buff/cache  available
> Mem:  54Mi   24Mi   6.7Mi  76Ki    27Mi        30Mi
> Swap: 255Mi  4.2Mi  251Mi
```

IPはご家庭のルーターのDHCP払い出し状況等などから該当するIPを探して接続します（例では10.255.255.7）。デフォルトパスワードは `riscv` です。
リポジトリがデフォルトで中国内ミラーに向いているのが気になりますが、それ以上に主流なパッケージがあまりない状況です。

# Webサーバにしてみたい
深く考えずに記事を書き、深く考えずに題材を決め、深く考えずにmilk-Vを起動したため、明確になにをしたいみたいなものがありません。そもそもタイトルが「買ってみた」なのでタイトルは現時点でも達成できているわけで、買ってみた＆起動してみたで終わる手もありますが、それだとおもしろくない。
なので、ひとまずWebサーバとして一枚のWebサイトを表示できるようにしてみたいと思います。Linuxが動作するデバイスの我流 `Hello, world` 的な感じです。
本来であればWebサーバはNGINXをインストールしてしまえば解決なのですが、上で試した通りNGINXパッケージがデフォルトだとありません。
NGINXをビルドする、他の方がビルドしたバイナリを探してくる、あるいはlighttpd（ライティ）などをビルドするなど様々な方法がありますが、今回はそれらすべて~~面倒だったため~~時間がかかりそうだったため、GoでWebサーバを書いてクロスコンパイルします。

### コード例
雑実装です。HTTPライブラリとして [gorilla/mux](https://github.com/gorilla/mux) を利用しています。
```golang
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"strconv"

	"github.com/gorilla/mux"
)

var serverSettings struct {
	FileDir string `json:"directory"`
	Port    int    `json:"port"`
}

func main() {
	var fileName string
	flag.StringVar(&fileName, "s", "./settings.json", "Configurations File Path")
	flag.Parse()
	err := readSettings(fileName)
	if err != nil {
		fmt.Printf("%v\n", err)
	}
	routingRequests(serverSettings.Port)
}

func readSettings(fileName string) (err error) {
	jsonDef := []byte(`{"directory":"./data","port":80}`)
	rawData, err := os.ReadFile(fileName)
	if err != nil {
		json.Unmarshal(jsonDef, &serverSettings)
		return fmt.Errorf("file error")
	}
	err = json.Unmarshal(rawData, &serverSettings)
	if err != nil {
		json.Unmarshal(jsonDef, &serverSettings)
		return fmt.Errorf("format error")
	}
	return nil
}

func routingRequests(port int) {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/{request}", requestHandler)
	log.Fatal(http.ListenAndServe(":"+strconv.Itoa(port), router))
}

func requestHandler(w http.ResponseWriter, r *http.Request) {
	request := mux.Vars(r)["request"]
	raw, err := os.ReadFile(serverSettings.FileDir + "/" + request)
	if err != nil {
		res, _ := json.Marshal(map[string]string{"error": err.Error()})
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusNotFound)
		w.Write(res)
	} else {
		w.Header().Set("Content-Type", "text/html")
		w.WriteHeader(http.StatusOK)
		w.Write(raw)
	}
}
```

### コンパイル例
GOOSでOS種別、GOARCHでアーキテクチャを設定します。かなり幅広いOSとアーキテクチャに対応しており、雑アプリを書くならやっぱりGolangかRustだなとなるなどしました。
```shell
$ GOOS=linux GOARCH=riscv64 CGO_ENABLED=0 go build -o binary -a -tags netgo -installsuffix netgo --ldflags '-extldflags "-static"' main.go
```

### 設定ファイルの用意（しなくてもデフォルト値があるので大丈夫）
```json
{
    "directory": "./data",
    "port": 80
}
```

### 起動する
起動させる際はがんばってmilk-Vにバイナリを転送し（ぼくはSDカードのbootに突っ込み、milk-V側からマウントして移動させました）、 `./binary` のように直接実行で起動できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3592296/913e6c02-8043-2c7d-dbd3-c015d2cea150.png" alt="うごいた">

# まとめ
なんだかよくわからない駄文になってしまいましたが、まとめると以下のような感じです。

- RISC-V関連はまだ発展途上なのでさわるだけでもかなりつらさがあり、おもしろい
- Golangはクロスコンパイルや静的ビルドが相当簡単にでき、ビルド周りだけは本当に優秀

ありがとうございました。