え！！社内ネットワーク内だけで Code with me を！？

**出来らあっ！**

---

この記事は [セゾン情報システムズ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/sisco) 15日目の記事です.

※当記事の内容は高頻度で更新され続けている EAP なプラグインの内容なので, 情報が古い可能性があります

## 概要

IntelliJ IDEA の [Code with me](https://blog.jetbrains.com/ja/idea/2020/09/code-with-me-eap-ja/) は, 同一のソースを複数人で編集することができる, VS Code の Live Share のようなプラグインです.
1人がソース公開用の URL を発行しその URL に他の誰かがアクセスすると, Code with me 専用クライアント (≠ IntelliJ IDEA) が起動してソースを共同編集できるようになります.

デフォルトの設定のまま使用すると, Jetbrains が無料で公開しているサーバが通信の中継をしてくれます.
この設定は Settings > Other Settings > Code with Me より変更することができます.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262975/906a9fd1-b0eb-725b-2553-77d56eee5b1a.png)

User Name の変更は誰もがやりたくなることでしょう.
しかし通信中継用サーバの URL を変更できたところで, 我々は変更先を持ち合わせていません.
これはなぜ変更できるようになっているのでしょうか？
**それは Lobby Server / Relay Server を我々が自前で用意する手段を Jetbrains が提供しているからです.**

当記事では, Lobby Server / Relay Server を自前で用意し, それを使って実際に疎通確認を行うところまでを示します.

## Lobby Server / Relay Server とは

深く知らなくてもとりあえず動いてくれるのでざっくりと説明すると,

* Lobby Server: ソース公開用の URL を発行するためのサーバ
* Relay Server: ソース編集内容の送受信をするために Websocket のセッションを張るためのサーバ

という感じです.
上記に加え, HTTPS 通信をリバースプロキシで実現するための nginx, セッション情報を保管する Redis を使うことになります.

## 公式ドキュメントを読む

**[Code With Me administration guide](https://www.jetbrains.com/help/cwm/code-with-me-administration-guide.html)** というものが存在します.
このページはかなり高頻度で更新されていますので, 気になる方は毎日チェックしてみてください.
ドキュメントだけでなくモジュールのバージョンも高頻度で更新されています.

## ファイルを用意する

では必要なファイルをすべて用意しましょう.

### ディレクトリを用意する

ダウンロードあるいはコピペしてきたファイルを配置するディレクトリ構造を作りましょう.
以下のように構成してください.

```
code-with-me
├ lobby
├ relay
└ nginx
  └ ssl
```

### Lobby Server / Relay Server をダウンロード

まずは Lobby Server / Relay Server の本体をダウンロードします.
[Code With Me administration guide](https://www.jetbrains.com/help/cwm/code-with-me-administration-guide.html) のページ上部にダウンロードページへのリンクがあります.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262975/c77fa4a0-3c2c-fa8d-d827-ebe58c0bdf85.png)

First name と Email address を入力し, 内容に同意して次に進むとダウンロードページが開きます.

`lobby-server-linux-x64.<バージョン>.tar.gz` は `lobby/` に,
`ws-relayd<バージョン>` は `relay/` にそれぞれ配置してください.
tar は解凍する必要はありません.

### Admin Guide を元に必要なファイルを作成

以下のファイル群をコピペで作成してください.

```Dockerfile:relay/Dockerfile
FROM alpine:latest
ARG DISTRIBUTION_VERSION=""
COPY ws-relayd$DISTRIBUTION_VERSION /ws-relayd
RUN chmod +x /ws-relayd
COPY lobby_public.pem /lobby_public.pem
CMD /ws-relayd -addr relay:3274 -jwt-key-type ecdsa -jwt-key-file /lobby_public.pem
```

```Dockerfile:lobby/Dockerfile
FROM debian:buster-slim

ARG DISTRIBUTION_VERSION=""

ADD lobby-server-linux-x64.${DISTRIBUTION_VERSION}.tar.gz /home/lobby-server
COPY lobby_private.pem /home/lobby-server/lobby_private.pem

RUN apt-get update && apt-get install -y unzip net-tools procps && apt-get clean

WORKDIR /home/lobby-server

ENV JAVA_HOME /home/lobby-server/jbr
ENV SERVER_PORT 2093
ENV BASE_URL https://lobby.cwm.internal
ENV ENABLED_FEATURES p2p_quic,direct_tcp,ws_relay
ENV FORCE_RELAY_URI wss://relay.cwm.internal
ENV RELAYS_ECDSA_JWT_KEY_FILE /home/lobby-server/lobby_private.pem
ENV REDIS_HOST redis
ENV REDIS_PORT 6379

ENTRYPOINT ["bin/lobby-server"]
```

```conf:nginx/nginx.conf
events {}
http {
    server {
        listen              443 ssl;
        server_name         relay.cwm.internal;
        ssl_certificate     /etc/ssl/nginx/relay.cwm.internal.crt;
        ssl_certificate_key /etc/ssl/nginx/relay.cwm.internal.key;

        location / {
            proxy_pass http://relay:3274;

            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "Upgrade";
            proxy_set_header Host $host;
        }
    }

    server {
        listen              443 ssl;
        server_name         lobby.cwm.internal;
        ssl_certificate     /etc/ssl/nginx/lobby.cwm.internal.crt;
        ssl_certificate_key /etc/ssl/nginx/lobby.cwm.internal.key;

        location / {
            proxy_pass http://lobby:2093;
        }
    }
}
```

```yml:docker-compose.yml
version: "3.8"
services:
  nginx:
    image: nginx:latest
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/ssl/nginx:ro
    ports:
      - 443:443
  relay:
    build:
      context: ./relay
      args:
        DISTRIBUTION_VERSION: 1.0
  lobby:
    build:
      context: ./lobby
      args:
        DISTRIBUTION_VERSION: 1.0
  redis:
    image: redis:latest
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - ./redis/data:/data
```

### docker-compose.yml を修正する

コピペしたままの docker-compose.yml では実は動作しません.
`DISTRIBUTION_VERSION` を書き換えてやる必要があります.
例えば筆者がダウンロードした時点では,

* lobby-server-linux-x64.1165.tar.gz
* ws-relayd1038

がダウンロードできましたので, これを docker-compose.yml に適用してやります.

```yml:docker-compose.yml
version: "3.8"
services:
  nginx:
    image: nginx:latest
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/ssl/nginx:ro
    ports:
      - 443:443
  relay:
    build:
      context: ./relay
      args:
        DISTRIBUTION_VERSION: 1038
        # DISTRIBUTION_VERSION: 1.0  ← デフォルト
  lobby:
    build:
      context: ./lobby
      args:
        DISTRIBUTION_VERSION: 1165
        # DISTRIBUTION_VERSION: 1.0  ← デフォルト
  redis:
    image: redis:latest
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - ./redis/data:/data
```

### 証明書を生成する

以下コマンドを実行して証明書を生成してください.
Windows であれば Git Bash で実行できます.

```sh
openssl ecparam -name secp384r1 -genkey -noout -out lobby/lobby_private.pem
openssl ec -in lobby/lobby_private.pem -pubout -out relay/lobby_public.pem
```

また, 上記とは別に, nginx 用に SAN (subject alternative name) を含む証明書を生成してやる必要があります.
...が, これは今日時点での Admin Guide に生成方法の記載が無いため筆者が調べた方法で生成します.
(よりよい方法があればぜひコメントください！)

筆者はこちらの記事を参考にさせていただきました: [【OpenSSL】SANでDNS情報を付与した証明書を作成する - かえでBlog](https://kaede.jp/2018/06/10191854/)
以下の例では Windows の Git Bash を用いています.

1. vi で `openssl.cnf` を開く

    ```sh
    $ vi /mingw64/ssl/openssl.cnf
    ```

1. `[ v3_req ]` という記載を探し, 設定を書き足す ([設定イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262975/1485fa40-27cf-2a48-7592-a81f1fee6faa.png))

    ```
    [ v3_req ]

    # Extensions to add to a certificate request

    basicConstraints = CA:FALSE
    keyUsage = nonRepudiation, digitalSignature, keyEncipherment
    # ↓ 以下を追記
    subjectAltName = @alt_names

    [alt_names]
    DNS.1 = lobby.cwm.internal
    IP.1 = <Lobby Server を立てるマシンの IP アドレス>
    # ↑ 以上を追記
    ```

1. Lobby Server 用の証明書を作成

    ```sh
    $ openssl req -extensions v3_req -new -newkey rsa:4096 -nodes -x509 -days 365 -keyout ./nginx/ssl/lobby.cwm.internal.key -out ./nginx/ssl/lobby.cwm.internal.crt
    Generating a RSA private key
    .......................................++++
    ..........................................................................................................++++
    writing new private key to './nginx/ssl/lobby.cwm.internal.key'
    -----
    You are about to be asked to enter information that will be incorporated
    into your certificate request.
    What you are about to enter is what is called a Distinguished Name or a DN.
    There are quite a few fields but you can leave some blank
    For some fields there will be a default value,
    If you enter '.', the field will be left blank.
    -----
    Country Name (2 letter code) [AU]:JP
    State or Province Name (full name) [Some-State]:Tokyo
    Locality Name (eg, city) []:city
    Organization Name (eg, company) [Internet Widgits Pty Ltd]:company
    Organizational Unit Name (eg, section) []:section
    Common Name (e.g. server FQDN or YOUR name) []:lobby.cwm.internal
    Email Address []:<自身のメールアドレス>
    ```

1. もう一度 vi で `openssl.cnf` を開く
1. 先程追記した `DNS.1 = lobby.cwm.internal` を `DNS.1 = relay.cwm.internal` に書き換える (必要であれば IP アドレスも)
1. Relay Server 用の証明書を生成

    ```sh
    $ openssl req -extensions v3_req -new -newkey rsa:4096 -nodes -x509 -days 365 -keyout ./nginx/ssl/relay.cwm.internal.key -out ./nginx/ssl/relay.cwm.internal.crt
    ```

## Lobby Server / Relay Server を起動する

これでサーバ側の用意はできましたので, サーバを起動してみましょう.

```sh
$ docker-compose up -d
```

手順にミスが無ければおそらく問題なく4つのコンテナが起動するかと思います.

## Code with me を使う

サーバ側の設定はできましたので, 次はクライアント側の設定をしましょう.

<!-- TODO: 用語怪しい... 用語の使い方合ってるか調べておく -->
nginx の設定上, サーバに対して IP アドレスではなく DNS 名でアクセスする必要があります.
そのため, Windows であれば `C:\Windows\System32\drivers\etc\hosts` に追記することでこれに対処します.

### hosts を編集する

以下のようにサーバ情報を hosts に追記してください.

```
<サーバの IP アドレス> lobby.cwm.internal
<サーバの IP アドレス> relay.cwm.internal
```

### IntelliJ IDEA の設定を変更する

冒頭に触れた Code with me の設定をここでようやく変更します.
以下画像のように設定を変更してください.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262975/fe10c89a-dd14-2d58-921b-f24ad8d340ab.png)

### 招待 URL を発行する

すべて設定ができたら, 以下画像のアクションから招待 URL を発行してください.

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/262975/9b8fcb2f-4145-dc6d-7f1a-c498a165c599.png)

## ENJOY!!

招待 URL が発行できればほぼ問題なく Code with me を使えるでしょう (おそらく) (きっと) (多分) (なお EAP)
自分で自分の招待 URL を踏むかたちでも接続テストはできるので試してみてください.

外部サーバに接続するせいで社内で Code with me を使えないそこのあなた ~~(筆者)~~, ぜひご自身でサーバを立ててみてください.