DiscordからMinecraftサーバーを立ち上げた話

# はじめに
こんにちは。陰キャ大学生です。
陰キャ大学生の私は友達がオンライン上にしかいません。そのため、よくオンラインゲームをします。今回はそのお話です。

大学生の私は生協マンションに住んでいるため、サーバーを立ててもポート解放できず遊べません。そのため、普段はAWSのEC2でサーバーを立てています。

このサーバーをボイスチャットツールのDiscordから起動及び停止できるようにすれば大変便利です。

それでは前置きが長くなりましたがどうぞ。

# EC2の準備
### 概要
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275720/96a217fa-9239-419a-289b-d2316bf8540f.png)

- __discord-bot EC2 t2.microインスタンス__
- __game-server EC2 t2.largeインスタンス__

システムの概略はこのような感じです。ここでのポイントは

- discordのレスポンス用とサーバー用に2つのインスタンスを用意
- それぞれのインスタンスはアカウント内セキュリティグループで管理

#### 2つのインスタンスを用意する理由
お金がないからです。minecraftのサーバーを動かすには最低でも__t2.large__の性能(メモリ8GB)が必要になります。しかしDiscordのbotは常にアクティブにする必要があり、より高価な__t2.large__を常時起動するわけにはいかないからです。

### インスタンスの設定
EC2のインスタンスの起動方法は多くの方が説明してくださっているので割愛します。

#### discord-bot
インスタンスタイプ：__t2.micro__
インバウンドルール：無編集でOK(SSHの22ポートのみ)
コンソール：AWSコマンドが使えること

discordのBOTは[discord.py](https://discordpy.readthedocs.io/en/latest/)を使用します。
どうしてPythonなのかというと、私が好きだからです。。

#### game-server
インスタンスタイプ：__t2.large__
インバウンドルール：TCP25565ポートを追加(Minecraft用)

# 実装
## discordのbotアカウントの作成
[こちら](https://note.com/bami55/n/ncc3a68652697)を参考に作成してください。
アクセストークンを控えておきます。

## discord-botの作成
__discord-botインスタンス__で作業します。
SSH接続したターミナルで以下を実行します。

```bash
python3 -m pip install -U discord.py
```

メインのソースコードを記述します。今回は`$start minecraft`と入力されたらサーバーを起動、`$stop minecraft`と入力されたらサーバーを停止するようにします。

```py


import discord
import subprocess
import paramiko
import time

# 自分のBotのアクセストークンに置き換えてください
TOKEN = 'your access token'
# game-serverのインスタンスidを指定してください
INSTANCEID = 'your instance id'

# 接続に必要なオブジェクトを生成
client = discord.Client()

# ***************************
# ***    処理関数
# ***************************
class DiscordBOT:
    def __init__(self, client):
        self.SSHClient = None

    async def main(self, discord_event):
        get_text = discord_event.content
        send_text = ""

        if "$start minecraft" in get_text:
            # インスタンスの起動
            subprocess.call("aws ec2 start-instances --instance-ids {}".format(INSTANCEID), shell=True)
            time.sleep(3)

            # インスタンスが起動するまで待機
            subprocess.call("aws ec2 wait instance-running --instance-ids {}".format(INSTANCEID), shell=True)
            time.sleep(3)

            # インスタンスのIPアドレスを取得
            proc = subprocess.run(["aws ec2 describe-instances --instance-ids {} --query 'Reservations[*].Instances[*].PublicIpAddress' --output text".format(INSTANCEID)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
            ip_add = proc.stdout.decode("utf-8")
            ip_add = ip_add.replace(".", "-").replace("\n", "")
            self.server_ip = ip_add
            time.sleep(3)

            # SSH接続クライアント作成
            self.SSHClient = paramiko.SSHClient()
            self.SSHClient.set_missing_host_key_policy(paramiko.WarningPolicy())
            self.SSHClient.connect('ec2-{}.compute-1.amazonaws.com'.format(ip_add), username='root', password='')
            time.sleep(2)
            
            # SSHでminecraftサーバー起動
            stdin, stdout, stderr = self.SSHClient.exec_command("java -Xmx4096M -Xms4096M -jar server.jar nogui")
            for x in stdout:
                print(x)
            for x in stderr:
                print(x)
            time.sleep(2)

            # 接続用のIPアドレスをdiscordに送信
            send_text = "インスタンスの起動とminecraftサーバーへの接続に成功しました。\n サーバー起動後は {} で接続できます".format(ip_add.replace("-", "."))

        elif "$stop minecraft" in get_text:
            # サーバーの停止
            self.SSHClient.connect('ec2-{}.compute-1.amazonaws.com'.format(ip_add), username='root', password='')
            time.sleep(2)
            stdin, stdout, stderr = self.SSHClient.exec_command("stop")
            self.SSHClient.close()

            # インスタンスの停止
            subprocess.call("aws ec2 stop-instances --instance-ids {}".format(INSTANCEID), shell=True)
            send_text = "サーバーの停止が完了しました。"

        if send_text:
            await discord_event.channel.send(send_text)


discordbot = DiscordBOT(client)

@client.event
async def on_ready():
    print('ログインしました')

# on get message
@client.event
async def on_message(message):
    if message.author.bot:
        return

    await discordbot.main(message)

# Botの起動とDiscordサーバーへの接続
client.run(TOKEN)
```

これをEC2上で実行すれば完成です。

```bash
nohup python3 main.py &
```

## game-serverの設定
javaをインストールする必要があります。
[こちら](https://qiita.com/neko_com/items/ef0409bc85b8054a6004)を参考にしてください。

さらにはrootユーザーのホームディレクトリにminecraftのサーバースクリプト(server.jar)を保存しておきます。

また、SSH接続を公開鍵認証を用いて行いますのでそちらの設定を行います。
こちらもすでに記事がありますので紹介させていただきます。 

[EC2インスタンス間でSSHパスワードなしでログインする方法](https://recipe.kc-cloud.jp/archives/6666)

## セキュリティグループの設定
いまのままでは、awsコマンドによっての遠隔起動ができません。そのためそれを許可するためにそれぞれを同じセキュリティグループに入れてやる必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275720/fe5bbd03-c117-b06a-52e4-c7caee0e56b6.png)

この__IAM ロールの割り当て/置換__を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275720/f45c54f6-b9e5-1d9c-eb08-078064f176e4.png)

新しいIAMロールを作成後、それぞれのインスタンスを同じIAMロールに追加して準備完了です。

# 結果

![daw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275720/fa4cf5e9-5105-040d-d66b-3b8688dfb951.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275720/1a6ceb6f-dc4e-888e-8cbd-bb6fe2fe94c5.png)

無事に動きました。めでたしめでたし。