Unityアプリケーションが自身で自身を更新する方法

# 実現したいこと
* **PC上のUnityアプリケーション**がサーバーと更新チェックを行い、更新が必要であればサーバーからInstallerをダウンロードして自ら再インストールする
* 今回はローカルサーバーを立てて、すべてローカル環境で行います

# 環境
## ホスト
* **Unity5.5**（バージョン依存はないと思うので、それ以外のバージョンでも動くと思います）
* **Windows10 Pro**（会社支給マシン）

## ゲスト
* ElementaryOS Loki0.4（なかみは**Ubuntu16.04LTS**）
  * 今回は**VirtualBox**でいれました
  * なぜかGuestAdditionalがインストールできなかったのでドラッグ&ドロップができず、共有フォルダ経由でやり取りするのがかなり面倒なので、**素直にUbuntuを使ったほうがいいかもです…**
* サーバーサイドは今回はnode.js使いましたが、RubyでもPHPでもなんでも大丈夫です

# 手順
## Unityアプリケーションをつくる
### シーン
* とりあえず起動時に更新チェックするようにしたいので、1つだけシーンを作ります
* CanvasにPanelをいれて、その中に更新ボタンと終了ボタンをつくります
* ![Unity 5.5.2f1 Personal (64bit) - Opening.unity - UpdateCheck - PC, Mac & Linux Standalone _DX11_ 2017-06-14 16.30.01.png](https://qiita-image-store.s3.amazonaws.com/0/89215/4fdced4f-d7df-7694-dab2-8dfddce715f2.png)

### スクリプト
* UpdateMenuController.cs をつくります
* StreamやProcessの処理は安全に処理できてないと思うのでコピペで使わないほうがいい気がします

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Diagnostics;

public class UpdateMenuController : MonoBehaviour {

    private string downloadURL;

    class Response {
        public string url = "";
    }

    IEnumerator Start() {
        WWW www = new WWW("http://192.168.1.100:8888/check");
        yield return www;
        Response res = JsonUtility.FromJson<Response>(www.text);
        if (res.url != "") {
            downloadURL = res.url;
        }
    }

    public void OnClickUpdateButton() {
        StartCoroutine("UpdateApplication");       
    }

    IEnumerator UpdateApplication() {
        WWW www = new WWW(downloadURL);
        yield return www;

        string path = Application.temporaryCachePath;
        string filePath = path + "/setup.exe";

        FileStream stream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite);
        BinaryWriter writer = new BinaryWriter(stream);
        writer.Write(www.bytes);
        writer.Flush();
        writer.Close();

        Process process = new Process();
        process.StartInfo.FileName = filePath;
        process.Start();

    }

    public void OnClickQuitButton() {
        Application.Quit();
    }
}
```
* あとは（どこでも大丈夫ですが）UpdateMenuに上記スクリプトをアタッチして、それぞれのボタンコンポーネントのonClick()にそれぞれの処理をバインドしてあげると動くはずです
* ここではIPを指定していますが、仮想マシンを立ててから `ifconfig` などで調べて適宜変更して下さい

### 注意点
* 実際に運用する際は最初のStartでのWWWの**URLにアプリケーションのバージョン情報や使用しているデバイス情報などのパラメーター**を付与して、サーバー側でそれに応じたInstallerのURLを返してあげるのがいいと思います
* ここでは必ずurlが返ってくる想定仕様にしていますが、更新が不要であれば、なにも起きないようにする分岐が必要になります

# Installerにコンパイルする
* まずUnityアプリケーションの**ビルド先はBuildsなどのフォルダ**を作ってそこにエクスポートするのがコンパイルするときに楽なので作って、そこに出力しておきましょう
* Unityアプリケーションをビルドしたら、**EXEファイルとXXX_Dataフォルダ**が生成されると思いますので、それをまるっとInstallerにコンパイルします

* こちらのブログを参考に、**InnoSetupCompiler**をつかってInstaller化します
  * http://martin-klappacher.com/tutorial-windows-installer-for-your-unity-game/ 
  * ほかにいいツールや方法などがあればご教授いただけると幸いです 
* またこのInnoSetupCompilerを使うのは自己責任でお願いします
* コンパイル後に出力されるファイルは `setup.exe` とします
  * また設定ファイルである`.issファイル` や `setup.exe` をBuildsの中に入れてしまうと**コンパイル時に一緒に含まれてしまうので別の場所に保存する**ようにしたほうがいいと思います

## VirtualBoxにゲストOSをいれる
### 環境設定
* [VirtualBoxの公式サイト](https://www.virtualbox.org/wiki/Downloads)から**WindowsHost版**をダウンロードしてインストールします。
* ISOイメージを[Elementaryの公式サイト](https://elementary.io/)からダウンロードしてインストールします
  * 寄付額のフォームがありますがカスタムを押して0と入力すれば無料でダウンロードできます 

### 仮想マシンの作成
* VirtualBoxを起動して、「新規」を押して仮想マシンの作成を行います
* ![仮想マシンの作成 2017-06-14 15.21.26.png](https://qiita-image-store.s3.amazonaws.com/0/89215/7a3c633c-47d9-d1a1-51a5-7c9f84cbd6f3.png)
* Elementaryは中身はUbuntuなのでバージョンはUbuntuで大丈夫です
* メモリサイズやハードディスク、ハードディスクのファイルタイプはだいたい**デフォルトのままで大丈夫です**

### OSインストール
* ![Oracle VM VirtualBox マネージャー 2017-06-14 17.16.01.png](https://qiita-image-store.s3.amazonaws.com/0/89215/899591e4-dccc-89cd-885d-a041db323f5d.png)
* 上記が終わって仮想マシンができたら、起動します
* 起動時にOSインストールするかどうか聞かれるので、とりあえずインストールします（ここはよくわかってない）

### OS初期設定
* インストール完了後に再起動して、デスクトップ画面が表示されたらまずターミナルを開いて更新をかけます

```shell-session
$ sudo apt-get update
$ sudo apt-get upgrade
```

* あとは好みでEditerを入れたりしてください
  * 自分は以下をインストールしました
    * Atom（エディター）
    * Chrome（ブラウザ）
    * mozc（Google日本語入力のLinux版）
    * linuxbrew（homebrewのlinux版）
      * Macとインストールパスが違うので注意
    * それぞれのインストール方法などは各公式サイトをご確認下さい

#### ネットワーク設定
* ホストからアクセスできるように設定します
* **仮想マシンを一度Shutdown**してから仮想マシン＞設定＞ネットワーク＞アダプター２に**ホストオンリーアダプター**を追加する
* 仮想マシン＞設定＞ネットワーク＞アダプター１（NAT）＞ポートフォワーディング＞高度＞**ホストとゲストのポート8888をあけておく**
* 上記設定が終わったら `ifconfig` でゲストOSのIPアドレスを調べます
  * ここでは `192.168.1.100` であると仮定します 

## node.jsでサーバーをつくる
### node.jsのインストール
* linuxbrewで[ndenv](https://github.com/riywo/ndenv)をインストールしました
  * ndenvはフォルダ単位でnodeのバージョンを指定できるので重宝してます 
* ndenvのinstallを使うには[node-build](https://github.com/riywo/node-build)が必要なのでそちらもインストールしておきます

```shell-session
$ brew install ndenv

$ echo 'export PATH="$HOME/.ndenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(ndenv init -)"' >> ~/.bash_profile
$ exec $SHELL -l

$ git clone https://github.com/riywo/node-build.git $(ndenv root)/plugins/node-build

$ ndenv install v6.11.0
$ cd ~/workspace/project
~/workspace/project$ ndenv local v6.11.0
~/workspace/project$ node -v
v6.11.0
```
※設定がうまくいかないときは公式サイトをご確認下さい

### サーバーをつくる
```shell-session
~/workspace/project$ npm init -y
```
* 上記を実行すると**package.json**が自動生成されます
 * パッケージの依存解決とかしてくれるんですが今回は標準ライブラリだけなので特にすることはないです
* 次に **index.js** を作ります

```js
var http = require('http');
var fs = require('fs');
var path = require('path');

var server = http.createServer();

server.on('request',function(req, res){
  if(req.url === "/check"){
    res.writeHead(200, {'Content-Type':'application/json; charset=utf-8'});
    res.write('{ "url":"http://192.168.1.100:8888/download" }');
    res.end();
  }else if(req.url === "/download"){
    var filePath = path.join(__dirname, 'assets/setup.exe');
    var stat = fs.statSync(filePath);

    res.writeHead(200, {
      'Content-Type':'application/octet-steam',
      'Content-Length':stat.size,
      'Content-Disposition':'attachment; filename=setup.exe'
    });
    var readStream = fs.createReadStream(filePath);
    readStream.pipe(res);
  }
});
server.listen(8888,'192.168.1.100');
console.log('Server running at http://192.168.1.100:8888');
```
* 特にnpm installする必要もなく標準ライブラリだけで動きます
* ここでは `http://192.168.1.100:8888/check` と `http://192.168.1.100:8888/download` というリクエストを想定しています
* `assetsフォルダ` に先程コンパイルしたInstaller（setup.exe）を配置します。ファイル構成は以下のようになるはずです

```shell-session
~/workspace/project$ tree
.
├── assets
│   └── setup.exe
├── index.js
└── package.json
```

### サーバーの起動
```shell-session
~/workspace/project$ node index.js
Server running at http://192.168.1.100:8888
```

* 起動したら一旦ゲストOSのブラウザで `http://192.168.1.100:8888` にアクセスしてみましょう
* 上記が問題なければ今度はホストからアクセスして試してみましょう！

# アプリから更新
* サーバーが動いており、ホストからの疎通も確認できたらさっそく、ホストでUnityアプリケーションを起動してUpdateButtonを押してみましょう！