はじめに
マイクラのサーバーをdiscordから誰でも起動できるようにしたくてdiscord botからEC2・マイクラサーバーの起動・停止を行えるようにしてみました。
EC2は従量課金制なので、起動していればその分だけお金がかかってしまいます。誰も使っていないのに起動しているのは無駄なので、だれでもdiscordから自由に起動と停止ができるようにしてみました。
この記事の対象者
- Pythonの基本的な構文が理解できている方
- EC2の制御をdiscord botから行ってみたい方
構成図
- t2.micro:discord bot用
- t2.medium:minecraft server用
discord bot用サーバーは常時動いており、minecraft用サーバーを必要なときに起動するような構成となっています。
使用ライブラリなど
- Python 3.7.10
- paramiko 2.8.1
- discord.py 1.7.3
構築手順
1.discord botのトークン発行
2.discord bot用サーバーの構築
3.minecraft用サーバーの構築
4.discord bot用サーバー→minecraft用サーバーのノンパス設定
5.discord bot起動
の流れになっています。
discordのbotトークン発行
こちらを参考にdiscordのbot用トークンを発行します。
発行したトークンは後で使うので控えておきます。
discord bot用サーバーの構築
discord bot用サーバーの構築を行います。
サーバーをたてる
EC2でdiscord bot用サーバーを作成します。
以下構成内容
インスタンス:t2.micro
OS:Amazon Linux 2
※そのほかはデフォルト設定
- 必要なライブラリのインストール
(AmazonLinux2ではPython3があらかじめインストールされているため、インストール不要)
python3 -m pip install -U discord.py
pip3 install paramiko
discord bot用のコードを書く
処理内容としては
1.minecraft用サーバーを起動する
2.minecraft用サーバーにSSH接続する
3.minecraftサーバーを起動する
となっています。
import discord
import subprocess
import paramiko
import time
# 自分のBotのアクセストークンに置き換えてください
TOKEN = '発行したトークンをここに貼り付けてください'
# game-serverのインスタンスidを指定してください
INSTANCEID = '後で作成するminecraft用サーバーのインスタンスidをここに入力してください'
# regionを指定
REGION = "minecraft用サーバーを作ったリージョンを書いてください"
# 接続に必要なオブジェクトを生成
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:
await discord_event.channel.send("サーバーを起動します。(起動には1分ほど時間がかかります。)")
# インスタンスの起動
subprocess.call(
"aws ec2 start-instances --instance-ids {} --region {}".format(INSTANCEID, REGION), shell=True)
time.sleep(1)
# インスタンスが起動するまで待機
subprocess.call(
"aws ec2 wait instance-running --instance-ids {} --region {}".format(INSTANCEID, REGION), shell=True)
time.sleep(1)
# インスタンスのIPアドレスを取得
proc = subprocess.run(["aws ec2 describe-instances --instance-ids {} --region {} --query 'Reservations[*].Instances[*].PublicIpAddress' --output text".format(
INSTANCEID, REGION)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
ip_add = proc.stdout.decode("utf-8")
ip_add = ip_add.replace("\n", "")
self.server_ip = ip_add
print("server_ip=", self.server_ip)
time.sleep(15)
# SSH接続クライアント作成
self.SSHClient = paramiko.SSHClient()
self.SSHClient.set_missing_host_key_policy(
paramiko.WarningPolicy())
self.SSHClient.connect(
self.server_ip, username="ec2-user", timeout=1.0)
time.sleep(2)
# SSHでminecraftサーバー起動
stdin, stdout, stderr = self.SSHClient.exec_command(
"cd /home/ec2-user/minecraft ; source startminecraft.sh")
for x in stdout:
print(x)
for x in stderr:
print("error:", x)
time.sleep(10)
# 接続用のIPアドレスをdiscordに送信
send_text = "インスタンスの起動とminecraftサーバーへの接続に成功しました。\n サーバー起動後は {} で接続できます".format(
ip_add.replace("-", "."))
elif "$stop minecraft" in get_text:
await discord_event.channel.send("サーバーを停止します。(この処理は1分ほど時間がかかります。)")
# サーバーの停止
self.SSHClient.connect(
self.server_ip, username="ec2-user", timeout=1.0)
time.sleep(2)
stdin, stdout, stderr = self.SSHClient.exec_command(
"cd /home/ec2-user/minecraft ; source stopminecraft.sh")
self.SSHClient.close()
time.sleep(60)
# インスタンスの停止
subprocess.call(
"aws ec2 stop-instances --instance-ids {} --region {}".format(INSTANCEID, REGION), 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)
minecraft用サーバーの構築
minecraftサーバーを建てた後に、起動と停止を制御するシェルスクリプトを書きます。
minecraftサーバーを建てる
基本的な構築方法はこちら
シェルスクリプトを作成する
minecraftサーバーの起動、停止用のスクリプトを作成します。~/minecraft/
に配置しています。
- 開始時の処理ではssh接続が切れても処理を継続させたい&追加でコマンドを入力したかったので、
screen
コマンドで
#! /bin/bash
# screenの名前
readonly SCREEN_NAME='minecraft'
screen -AmdS $SCREEN_NAME java -Xmx1024M -Xms1024M -Dlog4j2.formatMsgNoLookups=true -jar server.jar nogui pause
- 終了時は念のため以下のプロセスを踏んでいます。
1.マイクラ内にアナウンス
2.マイクラサーバーを停止
3.停止コマンドを実行したら30秒待機(マイクラサーバーが停止するまで待機)
#! /bin/bash
# screen name
readonly SCREEN_NAME='minecraft'
if screen -ls | grep -o ${SCREEN_NAME}; then
# 停止開始
echo [date '+%F %T'] 'server stop script start'
# サーバー内にアナウンス
screen -S $SCREEN_NAME -X stuff 'say 30秒後にサーバーを停止します\n'
sleep 30s
# saveコマンド発行
screen -S $SCREEN_NAME -X stuff 'save-all\n'
sleep 5s
# stopコマンド発行
screen -S $SCREEN_NAME -X stuff 'stop\n'
# 停止実行待機
sleep 30s
else
echo [date '+%F %T'] 'server is not runnning'
fi
discord bot用サーバー→minecraft用サーバーのノンパス設定を行う
開始シェルや終了シェルを実行したいので、ノンパス設定を行います。
書くと記事が長くなってしまうので、こちらを参考にしてください。
IAMロールの設定
discord bot用サーバーからminecraft用サーバーの起動・停止を制御するために行います。
1.IAMロールを作成する
2.IAMロールには以下の権限を付ける
- 全部のリソースに対して
- DescribeInstances
- DescribeInstanceStatus
- ARN指定
- StartInstances
- StopInstances
3.IAMロールをdiscord bot用サーバーに付与する
discord botを起動
ssh接続が切れても処理が継続されるように、以下のコマンドで実行する
nohup python3 main.py &
動作確認
minecraftのサーバーが立ち上がることを確認できました。
まとめ
今回はdiscordからminecraftサーバーの起動・停止を制御できるようにしてみました。
今後はこちらのツールをterraformを用いて管理したり、discordのスラッシュコマンド機能を利用したものに移行することなどを行っていきたいと思っています。