はじめに
初めてQiitaを使うのでおかしなところがあったらごめんなさい。
できること
・MinecraftのModサーバーをローコストで運用
・Discord BotによるMinecraftサーバーの起動、停止、IPアドレスの確認、ステータスの
確認、Modファイルの操作(アップロード、リストアップ、削除)
きっかけと概要
本当はマインクラフトサーバーを自宅PC上で構築してみんなでマルチしたかったのですが、家のネットが共用サーバーのためポート開放ができず。
VPNを使う方法はクライアントで毎回接続するのが面倒なので、いろいろ方法を探した結果、GoogleCloudのCompute Engine(以下GCE)が無料で90日間、あるいは300ドル分のクレジットを使用できるとのことでGCEサーバーの構築を決意。(これをする前に一度AWSのEC2でサーバー運用していたのですが、なんせ料金が高い...)
しかし、24時間常に起動するのは料金的に現実的ではないし、24時間サーバーを開く必要もないので、Discordでサーバーを起動、停止、IPアドレスの確認、Modファイルの操作をできるようにする必要もある。エンドポイントやらなんやらを利用してサーバーレスでDiscord Botを運用できるような記事を見たが、以前試したときなぜかうまくできなかったため、今回はEC2の無料利用枠をつかってDiscord Botを24時間運用することに。Discord BotはPythonスクリプト1つを動かすだけなのでスペックは一切気にする必要なし。
EC2とGCEはgcloud CLIをつかって、EC2上でGCEをコマンドライン操作できるように設定することで起動、IPアドレスの確認、停止が可能。さらにEC2とGCEをSSH接続することでModファイルの操作も可能に。
本文
用意するもの
- やる気と根気
- GoogleCloudアカウント
- AWSアカウント
- Discordアカウント
- ChatGPTアカウント(あったらデバッグに超便利)
VM(仮想マシン)の構成
・GCEサーバー → Minecraft Modサーバー
・EC2サーバー → Discord Botサーバー
やることリスト
Minecraftサーバーの構築(VMで行う操作は(GCE)または(EC2)と語尾に記載)
- GoogleCloudの登録(90日間無料、要クレカ)
- インスタンスの作成
- ポート開放
- Javaのインストール(GCE)
- NeoForgeのインストール(Mod要らないなら通常サーバーのインストール)(GCE)
- Pythonのインストール(GCE)
- サーバー自動停止スクリプトの作成 (GCE)
- ワールドデータバックアップスクリプトの作成 (GCE)
- 上記7, 8のcrontabへの登録(GCE)
- サーバー自動起動のスクリプトの作成(GCE)
- 作成したファイルへの権限付与(GCE)
Discord Botサーバーの構築とBotの作成
0. AWSの登録(無料利用枠はアカウント登録から12か月間、毎月750時間まで無料)
0. Discordのアカウント登録(さすがに無料。ていうかみんな持ってるでしょ。)
- Discord Dev PortalでBotの作成(無料)
- AWS EC2インスタンスの作成
- Python3のインストール(EC2)
- gcloud CLIのインストールと初期設定(EC2)
- BotのPythonスクリプトの作成(EC2)
- サーバー自動停止時のメッセージ送信スクリプトの設定(EC2)
- SSHの設定(EC2)
Minecraftサーバーの構築
1. GoogleCloudの登録
ここからGoogleCloudに登録します。登録方法に関しては省略します。
2. インスタンスの作成
まずMinecraftのワールドを動かすサーバーを作っていきます。
左上のGoogleCloudのロゴがある左側の3本線をクリックして、Compute Engine → VMインスタンスをクリック。
次に、青色のインスタンスを作成ボタンをクリック。マシンの構成を以下のように設定。
・名前:minecraft-server
マシン名のタイポには気を付けてください(後になぜかうまくいかなくて結構悩んだ)
・リージョン:asia-northeast1(東京)またはasia-northeast2(大阪)
・ゾーン:asia-northeast1-b またはa, c
これらは後で設定時に指定する必要があるので覚えておく。(後で確認可能)
こちらの記事を参考に、インスタンスタイプを決定します。
今回はModを導入するので、あるコスト料金がかさまずに(90日間で300ドルいかないような)高性能なインスタンスを作成するため、以下のように設定しました。
Modの導入が少ない場合や人数が少ない場合はプリセットのn2d-highcpu-4でいいかも。
次に、OSとストレージタブに移動し、変更をクリック。OSをUbuntuにします。
(ここはお好みで。OSによってはコマンドが違ったり追加料金がかかるのでわからなければUbuntuにしてね。)
サイズはそこまで大きくなくてもいいけど20GBが無難。
(画像では10GBのままですが気にしないでください。変えるの忘れてました。)
左側のネットワーキングタブに移動し、ネットワークタグをminecraft-serverにする。
少し下に移動して、ネットワークインターフェースの確認を行う。画像のようになっていればOK。
ここまで出来たら、下の作成ボタンをクリックしてインスタンスを作成する。
3. ポート開放
次に、ナビゲーションバー(3本線)からVPC ネットワーク → ファイアウォールを選択
上部のファイアウォールルールの作成をクリックし、名前をネットワークタグと同じ名前に設定し、送信元IPv4範囲を0.0.0.0/0で指定する。その下のプロトコルとポートをTCPにチェックし、ポート番号25565を入力する。ここまで出来たら下の作成をクリック。
4. Java(OpenJDK)のインストール
ここからGCEで使用するコマンドは、Ubuntuのコマンドのため、ほかのOSとのコマンドとは異なります。
ようやくサーバーを起動することができます。ナビゲーションバー → VMインスタンスから、先程作成したインスタンスのチェックを入れて開始/再開をクリックします。インスタンスが起動まで少し待ちます。
起動したらSSHをクリックし、出てきたウィンドウでAuthorizeをクリック。
するとコンソールが立ち上がります。ここから様々な操作を行います。まずはJava(OpenJDK)のインストールから行きます。
あ、その前にaptのアップデートから行います。
$ sudo apt update
ここで、Minecraftをプレイするバージョンの確認を行います。Minecraftはゲームバージョンによって使用するJavaのバージョンが違うので、以下の表を参考にバージョンを指定してください。
Minecraft バージョン | Java バージョン | インストールコマンド例 |
---|---|---|
1.7.10~1.16.5 | Java8またはJava11 | sudo apt install -y openjdk-8-jdk |
1.17~1.17.1 | Java16 | sudo apt install -y openjdk-16-jdk |
1.18~1.20.4 | Java17 | sudo apt install -y openjdk-17-jdk |
1.20.5~ | Java21 | sudo apt install -y openjdk-21-jdk |
今回は1.21.1で遊ぶので以下のコマンドでインストールしました。
$ sudo apt install -y openjdk-21-jdk
インストールが完了したら、以下のコマンドでできているか確認します。
$ java -version
openjdk version "21.0.6" 2025-01-21
OpenJDK Runtime Environment (build 21.0.6+7-Ubuntu-120.04.1)
OpenJDK 64-Bit Server VM (build 21.0.6+7-Ubuntu-120.04.1, mixed mode, sharing)
5. NeoForgeのインストール
Modサーバーを作りたいので、NeoForgeのインストールを行います。他のModローダーの場合は読み替えてください。
まず、minecraftディレクトリを作成します。
$ mkdir minecraft
次に、自分のPCでNeoForgeのダウンロードページまで行きます。今回は1.21.1なので対応するバージョンを探してダウンロードボタンを右クリック → リンクのアドレスをコピーでダウンロード先のリンクをコピーします。
次に、作成したディレクトリに移動してから、以下の通りにGCEのコンソールで入力します。(URLの部分はコピーしたURLに置き換えてください。)
$ cd minecraft
$ sudo wget "<URL>"
// インストール例
$ sudo wget "https://maven.neoforged.net/releases/net/neoforged/neoforge/21.1.141/neoforge-21.1.141-installer.jar"
これでmkdirディレクトリにダウンロードできたと思います。
lsコマンドで確認してください。
$ ls
neoforge-21.1.141-installer.jar
このファイルを解凍します。以下のコマンドで解凍します。
// NeoForge
$ java -jar neoforge-21.1.141-installer.jar
最後にこのように出力されたら成功です。
The server installed successfully
You can delete this installer file now if you wish
※Forgeの場合は以下のようにしてください。
// Foge
$ java -jar minecraftforge-installer.jar --installServer
※Fabricの場合は以下の通りにしてください。
// Fabric
java -jar fabric-installer.jar server
ここまでできれば、サーバーを起動できます。lsコマンドで確認すると、run.shが生成されているはずです。
$ ls
libraries neoforge-21.1.141-installer.jar.log run.sh user_jvm_args.txt
neoforge-21.1.141-installer.jar run.bat server.jar
生成されていることが確認出来たら、run.shを編集していきます。
以下のコマンドで編集を行い、完了したらCtl + Sで保存し、Ctl + Xでnanoを抜けます。
$ nano user_jvm_args.txt
使用するメモリ量を指定する以下の二行を一番下に追加します。
-Xmx5G -Xms512M
※メモリの数値に関してはマシンのスペックに合わせて変えてください。マシンのRAMギリギリまで使用すると逆にサーバーの動作が重くなってしまいます。少なすぎず、多すぎずのリソースを確保してください。
これができたら、Ctl + Xでnanoを抜けて、run.shを起動します。最初は起動に失敗します。
$ ./run.sh
ここで、run.shとuser_jvm_args.txtの二つに権限を付与します。
chmod +x run.sh
chmod +x user_jvm_args.txt
次に、以下のコマンドでeula.txtを編集します。
$ nano eula.txt
falseになっている部分をtrueに変更します。
eula=true
保存したらnanoを抜けて、run.shを起動します。
$ ./run.sh
最後の行に以下が出力されれば起動成功です。
[Server thread/INFO] [ne.ne.ne.se.pe.PermissionAPI/]: Successfully initialized permission handler neoforge:default_handler
ここでサーバーに入れるかどうかの確認を行います。ブラウザのGCEのインスタンス一覧の画面で、〇の部分にIPアドレスが表示されているはずです。
ここのIPアドレスをコピーし、MinecraftのIPアドレス欄に打ち込んでください。
これでワールドに入れたら成功です。確認出来たらGCEコンソールでstopと打ち込んで終了します。
最後に、このMinecraftサーバーのコンソールをスクリーンを利用してデタッチします。(裏画面で起動)
まずはScreenをインストールします。
$ sudo apt install screen
次に、screenを利用してサーバーを建てるシェルスクリプトを作成します。
$ nano start_with_screen.sh
中身は以下のとおりです。
YOURUSERNAMEは変更してください。
#!/bin/bash
screen -dmS minecraft /home/<YOURUSERNAME>/minecraft/run.sh
ここまで出来たらMinecraftサーバーの起動部分はおおむね完成です。
6. Pythonのインストール
※Minecraftサーバーはstopと打ち込むと停止します。
次に、インスタンス自動停止のためのPythonのインストールを行います。最新バージョンがインストールされます。
$ sudo apt-get install -y python3
インストールが完了したら、確認を行います。このように出力されれば成功です。
$ python3 --version
Python 3.9.20
7. サーバー自動停止スクリプトの作成
Pythonのインストールができたら、次はワールド内のプレイヤー数を数えて0人の場合は自動でインスタンスを停止させるPythonスクリプトを作成します。@ekkoさんの記事を参考にさせていただきました。
$ nano ~/minecraft/shutdown.py
ワールド内にいる人数をログファイルから認識し、0人の場合は自動でサーバーをシャットダウンするスクリプトです。
7行目のはGCEアカウントのユーザー名(ユーザーID)に変更してください。
その他ディレクトリも変更している場合は修正してください。
import os
import sys
import datetime
import subprocess
# GCEアカウントのユーザー名
username = "<YOURUSERNAME>"
# 今日の日付のログファイルのパスを指定
today = datetime.date.today()
file_path = "/home/{username}/minecraft/logs/latest.log"
# ログファイルが存在しているかを確認
# ファイルが存在しない場合にはプログラムを終了
d = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
is_file = os.path.isfile(file_path)
if is_file:
pass
else:
text = "{} No log found".format(d)
print(text)
sys.exit()
# 「joined」と「left」のカウント用変数(初期化)
join_cnt = 0
left_cnt = 0
# ログファイルを開き「joined」と「left」の数をそれぞれカウント
with open(file_path) as log_file:
for line in log_file:
if "joined" in line:
join_cnt += 1
elif "left" in line:
left_cnt += 1
# 「joined」と「left」が同数の場合には停止スクリプトを実行し1分後にインスタンスを停止
# 同数でない場合には「誰かがログイン中」と表示
if join_cnt == left_cnt:
subprocess.call(["python3", "/home/{username}/minecraft/notify_shutdown.py"])
subprocess.call(["bash", "-c", "/home/{username}/minecraft/stop.sh"])
subprocess.call(["sudo", "shutdown", "-h", "+1"])
else:
print("someone is online")
8. ワールドデータバックアップスクリプトの作成
サーバー運用するにあたり、バックアップは必須と言っても過言ではありません。
(過去にバックアップなしで運用して、なぜかワールドが上書き生成され、数十時間を水の泡にした経験あり)
ということで、ディレクトリとシェルスクリプトを作成します。バックアップは1時間に1回、24時間分のバックアップを作成します。
このシェルスクリプトは、最終更新日が24時間前のファイルを全て削除します。サーバーの起動に日が空く場合はバックアップの方法を変更してください。(例:バックアップファイルは最新の2つを保持する等)
まずはディレクトリの作成を行います。
$ mkdir ~/minecraft/backups
$ nano ~/minecraft/backup.sh
シェルスクリプトの作成
YOURUSERNAMEは適宜変更してください。
#!/bin/bash
# === 設定 ===
WORLD_DIR="/home//minecraft/world"
BACKUP_DIR="/home/<YOURUSERNAME>/minecraft/backups"
TIMESTAMP=$(date +"%Y%m%d_%H%M")
BACKUP_FILE="$BACKUP_DIR/world_$TIMESTAMP.tar.gz"
# === バックアップ先がなければ作成 ===
mkdir -p "$BACKUP_DIR"
# === バックアップ作成 ===
tar -czf "$BACKUP_FILE" -C "$WORLD_DIR" .
# === 24時間以上前のバックアップを削除 ===
find "$BACKUP_DIR" -type f -name "world_*.tar.gz" -mmin +1440 -delete
権限付与も忘れずに。
$ chmod +x backup.sh
9. 7, 8のcrontabへの登録
crontabを使うことで定期的にスクリプトを動かすことができます。バックアップとシャットダウンのスクリプトをcrontabを使ってそれぞれ1時間毎と10分毎に行うようにします。
crontabの編集を行います。crontabの初回起動時は、エディタをどれに選択するかを問われます。なんでもいいですがnanoが一番使いやすいので<1>を入力してnanoを使います。
crontabを開きます。
$ crontab -e
crontabが開いたら、以下の二行を追加します。時間の設定方法については省略します。
*/10 * * * * python3 /home/<YOURUSERNAME>/minecraft/shutdown.py
0 * * * * /home/<YOURUSERNAME>/minecraft/backup.sh >> /home/<YOURUSERNAME>/minecraft/backup.log
これでcrontabへの登録は完了です。
10. サーバー停止時のワールド保存スクリプトの作成
サーバーを閉じる際に、セーブせずにインスタンスをシャットダウンするとワールドデータが壊れる可能性があるので、ワールドデータをセーブするシェルスクリプトを作成します。
まずはファイルを作成します。
$ nano ~/minecraft/stop.sh
次に、以下の内容で書き込みます。
#!/bin/bash
# Minecraftサーバーの screen セッション名
SESSION="minecraft"
echo "セーブ&停止処理を開始します..."
# save-all コマンド送信
screen -S "$SESSION" -p 0 -X stuff "save-all$(printf \\r)"
echo "ワールドを保存中..."
# セーブ完了待ち(1〜2秒)
sleep 2
# stop コマンド送信
screen -S "$SESSION" -p 0 -X stuff "stop$(printf \\r)"
echo "サーバーを安全に停止しました。"
これで自動シャットダウン時にMinecraftサーバーのワールドデータが自動で保存されるようになります。
11. サーバー自動起動のスクリプトの作成
これでGCEサーバー側は最後です。(執筆がんばれ俺)
特定のタイミングでスクリプトを実行できるsystemdを使用します。
まずはsystemdのディレクトリまで移動します。
$ cd /etc/systemd/system
次にサービスファイルを作成します。
$ nano auto_shell.service
以下の内容で書き込みます。必ずパスはフルパスを利用してください。でないとシステムに怒られます。毎度のとおりYOURUSERNAMEは適宜変更してください。
[Unit]
Description=minecraft service
After=network.target
[Service]
User=<YOURUSERNAME>
ExecStart=/home/<YOURUSERNAME>/minecraft/start_with_screen.sh
ExecStop=/usr/bin/screen -S minecraft -X quit
WorkingDirectory=/home/<YOURUSERNAME>/minecraft
Restart=always
Type=forking
[Install]
WantedBy=multi-user.target
これができたら、登録と起動を行います。
Minecraftサーバーが起動していない状態で行ってください。 systemctl startの際に25565ポートが使用済みでサーバーが起動できず、エラー状態になります。
$ sudo systemctl daemon-reload
$ sudo systemctl enable auto_shell.service
$ sudo systemctl start auto_shell.service
完了したら、auto_shell.serviceの確認を行います。
このような出力になれば正常に動作しています。
sudo systemctl status auto_shell
● auto_shell.service - minecraft service
Loaded: loaded (/etc/systemd/system/auto_shell.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2025-03-31 10:26:21 UTC; 1h 18min ago
Process: 522 ExecStart=/home/<YOURUSERNAME>/minecraft/start_with_screen.sh (code=exited, status=0/SUCCESS)
Main PID: 616 (screen)
Tasks: 46 (limit: 9510)
Memory: 1.1G
CGroup: /system.slice/auto_shell.service
├─616 SCREEN -dmS minecraft /home/<YOURUSERNAME>/minecraft/run.sh
├─624 bash /home/<YOURUSERNAME>/minecraft/run.sh
└─625 java @user_jvm_args.txt @libraries/net/neoforged/neoforge/21.1.140/unix_args.txt
Mar 31 10:26:20 minecraft-server systemd[1]: Starting minecraft service...
Mar 31 10:26:21 minecraft-server systemd[1]: Started minecraft service.
12. 作成したファイルへの権限付与
最後に権限を付与します。
$ chmod +x /etc/systemd/system/auto_shell.service
$ chmod +x ~/minecraft/stop.sh
$ chmod +x ~/minecraft/backup.sh
$ chmod +x ~/minecraft/shutdown.py
$ chmod +x ~/minecraft/start_with_screen.sh
$ chmod +x ~/minecraft/run.sh
$ chmod +x ~/minecraft/user_jvm_args.txt
これでGCEサーバー側での作業は(※)1つを除いて完了(notify_shutdown.pyが未作成)です。次にDiscord Botを運用するEC2側の作業に進みます。(涙目)
※Discord Botの作成によるトークンと通知先のチャンネルIDが必要なのでGCEサーバーは未完成ですが一度先に進みます。
Discord Botサーバーの構築
Discord のアカウント登録、EC2のアカウント登録については省略します。させてください。
1. Discord Botの作成
Discord Dev Portalにアクセスし、左側のApplications → 右側のNew Applicationを選択。
名前を入力して、チェックボックスにチェックを入れてCreate
作成できたら、左側のナビゲーションバーからBotをクリック。Add Botをクリックします。続けて**Yes, do it!**をクリックし、BOTを作成します。
次に、その下のReset Tokenボタンをクリックし、トークンを取得します。
このページを閉じるとトークンは二度と表示されないので注意!!
警告
このトークンは第三者に流出しないように気を付けてください。このトークンがあるとBOTが乗っ取られます。
次に、少し下のMessage Content IntentのチェックをONにしてください。
そして、左のタブからOAuth2を選択し、botにチェックマークを入れ、下に出てきたSend Messagesにチェックを入れ、その他必要な権限があれば付与させてください。
画面下部のURLをコピーしてブラウザで開き、管理者権限のあるサーバーに招待してください。
ここまで出来たらBot本体の作成は完了です。
2. EC2インスタンスの作成
AmazonWebServiceのEC2インスタンスを利用して、DiscordBotを運用します。
アカウント登録は自力で頑張ってください。
アカウント登録ができたら、EC2インスタンスを作っていきます。ここからインスタンスの作成を行います。
右上のパシフィックが「アジアパシフィック(東京)」になっていることを確認し、左側のメニュー → インスタンスをクリック。
右の黄色いボタンのインスタンスを起動をクリック
名前をdiscord_botとし、キーペアを同様の名前で作成・ダウンロードする。
他はデフォルトでOK。
そしたら右のインスタンスを起動のボタンを押して、起動が完了したらインスタンスに接続 → 接続で接続する。
3. Pythonのインストール
ここからEC2のターミナルでの作業に移行します
こちらも同様にPythonのインストールをしていきます。まずはパッケージのアップデートから。
$ sudo yum update -y
まぁ多分この出力になると思うけど、、、
$ sudo yum update -y
Amazon Linux 2023 Kernel Livepatch repository 99 kB/s | 15 kB 00:00
Dependencies resolved.
Nothing to do.
Complete!
python3をインストール
$ sudo yum install python3 -y
どうやら標準でインストールされてたらしいです。
$ sudo yum install python3 -y
Last metadata expiration check: 0:02:33 ago on Mon Mar 31 12:00:18 2025.
Package python3-3.9.21-1.amzn2023.0.2.x86_64 is already installed.
Dependencies resolved.
Nothing to do.
Complete!
ちゃんと入ってた。
$ python3 --version
Python 3.9.21
4. gcloud CLIのインストール
gcloudは、GoogleCloudをCLIで操作できるツールです。ファイル操作に必要です。
まず、依存関係のあるファイルをインストールします。
$ sudo yum install -y curl python3 unzip
ホームディレクトリにダウンロードして、展開します。
$ curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz
$ tar -xf google-cloud-cli-linux-x86_64.tar.gz
gcloud CLIをインストールします。気にせずEnter連打。
$ ./google-cloud-sdk/install.sh
次に、パスを通します。
$ source ~/google-cloud-sdk/path.bash.inc
バージョンの確認
以下のように出力されたら成功です。
$ gcloud version
Google Cloud SDK 516.0.0
bq 2.1.14
bundled-python3-unix 3.12.8
core 2025.03.24
gcloud-crc32c 1.0.0
gsutil 5.33
初期設定を行います。
$ gcloud init
Welcome! This command will take you through the configuration of gcloud.
(中略)
You must sign in to continue. Would you like to sign in (Y/n)? y
Go to the following link in your browser, and complete the sign-in prompts:
https://accounts.google.com/o/oauth2/auth?...
Once finished, enter the verification code provided in your browser:
出力されるURLに飛んで、許可しまくったら表示されるコードをコピーして、ターミナルに貼り付ける。(この時のアカウントはGCEサーバーを作成したアカウントでログインしてください)
コードをターミナルに入力し、正しく認証されると次のような出力が得られます。
You are signed in as: [mailaddress].
Pick cloud project to use:
[1] active-dahlia-455113-n9
[2] Enter a project ID
[3] Create a new project
Please enter numeric choice or text value (must exactly match list item): 1
どのプロジェクトを選択するか聞かれるので、「1」を入力してEnter。すると1行目の出力が出るので、yを押して続行します。
GCEサーバーを作成するときに、選択した場所を選んでください。asia-northeast1-bで作成したので、「32」を入力します。
Do you want to configure a default Compute Region and Zone? (Y/n)? y
Which Google Compute Engine zone would you like to use as project default?
If you do not specify a zone via a command line flag while working with Compute Engine resources, the default is assumed.
[1] us-east1-b
[2] us-east1-c
[3] us-east1-d
[4] us-east4-c
[5] us-east4-b
(中略)
[26] asia-east1-b
[27] asia-east1-a
[28] asia-east1-c
[29] asia-southeast1-b
[30] asia-southeast1-a
[31] asia-southeast1-c
[32] asia-northeast1-b
[33] asia-northeast1-c
[34] asia-northeast1-a
[35] asia-south1-c
(中略)
[48] asia-east2-b
[49] asia-east2-c
[50] asia-northeast2-a
Did not print [78] options.
Too many options [128]. Enter "list" at prompt to print choices fully.
Please enter numeric choice or text value (must exactly match list item): 32
これでgcloud CLIの初期設定は完了です。
5. Pythonスクリプトの作成
EC2でもPythonスクリプトを作成していきます。こちらのスクリプトは、Discordのメッセージを受信してメッセージの内容に応じてGCEインスタンスを操作するスクリプトです。
まず、必要なパッケージをインストールしていきます。
pip3をインストール(多分しなくてもよい)
$ python3 -m ensurepip --upgrade
$ pip3 --version
discord.pyのインストール
$ pip3 install -U discord.py
準備が整ったので、Pythonスクリプトを作成します。
$ nano discord_bot.py
import discord
import subprocess
import os
GCLOUD = "/home/ec2-user/google-cloud-sdk/bin/gcloud"
TOKEN = "<BOTACCESSTOKEN>"
INSTANCE = "minecraft-server"
ZONE = "asia-northeast1-b"
GCE_USER = "<YOURUSERNAME>" # GCEのユーザー名(例:ec2-user)
MOD_REMOTE_DIR = "/home/{GCE_USER}/minecraft/mods"
LOCAL_TMP_DIR = "/tmp/mc_mods"
# 初期化
os.makedirs(LOCAL_TMP_DIR, exist_ok=True)
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
# ===== 共通関数 =====
def run_gcloud_command(command_args):
try:
result = subprocess.run(
command_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if result.returncode != 0:
return None, result.stderr.strip()
return result.stdout.strip(), None
except Exception as e:
return None, str(e)
def get_ip():
return run_gcloud_command([
GCLOUD, "compute", "instances", "describe", INSTANCE,
"--zone", ZONE,
"--format=get(networkInterfaces[0].accessConfigs[0].natIP)"
])
def get_status():
return run_gcloud_command([
GCLOUD, "compute", "instances", "describe", INSTANCE,
"--zone", ZONE,
"--format=get(status)"
])
def start_instance():
return run_gcloud_command([
GCLOUD, "compute", "instances", "start", INSTANCE,
"--zone", ZONE
])
def stop_instance():
return run_gcloud_command([
GCLOUD, "compute", "instances", "stop", INSTANCE,
"--zone", ZONE
])
def scp_to_gce(local_path, remote_name):
remote_path = f"{GCE_USER}@{INSTANCE}:{MOD_REMOTE_DIR}/{remote_name}"
return run_gcloud_command([
GCLOUD, "compute", "scp", local_path, remote_path,
"--zone", ZONE
])
def ssh_to_gce(command):
return run_gcloud_command([
GCLOUD, "compute", "ssh", f"{GCE_USER}@{INSTANCE}",
"--zone", ZONE,
"--command", command
])
# ===== Discord Bot イベント =====
@client.event
async def on_ready():
print(f"✅ Bot 起動完了: {client.user}")
@client.event
async def on_message(message):
if message.author.bot:
return
content = message.content.lower()
if content == "!ip":
await message.channel.send("📡 IPアドレスを取得中...")
ip, err = get_ip()
if err:
await message.channel.send(f"❌ 取得失敗:\n```\n{err}\n```")
elif ip:
await message.channel.send(f"🌐 IPアドレス: `{ip}`")
else:
await message.channel.send("⚠️ IPが取得できません(インスタンスが停止中かも)")
elif content == "!status":
status, err = get_status()
if err:
await message.channel.send(f"❌ ステータス取得失敗:\n```\n{err}\n```")
else:
await message.channel.send(f"🖥️ サーバーの状態: **{status}**")
elif content == "!start":
await message.channel.send("🚀 サーバーを起動中です...")
_, err = start_instance()
if err:
await message.channel.send(f"❌ 起動失敗:\n```\n{err}\n```")
else:
await message.channel.send("✅ 起動完了!IPを取得します...")
ip, err = get_ip()
if ip:
await message.channel.send(f"🌐 IPアドレス: `{ip}`\n🕒 ゲームサーバーの起動まで約20秒かかります。")
else:
await message.channel.send("⚠️ IPがまだ割り当てられていません。少し待ってから再度 `!ip` を試してください。")
elif content == "!stop":
await message.channel.send("🛑 サーバーを停止中です...")
_, err = stop_instance()
if err:
await message.channel.send(f"❌ 停止失敗:\n```\n{err}\n```")
else:
await message.channel.send("✅ サーバーを停止しました。")
elif content.startswith("!addmod"):
if not message.attachments:
await message.channel.send("❗ 添付ファイルが見つかりません。modファイルを添付してください。")
return
for attachment in message.attachments:
filename = attachment.filename
local_path = os.path.join(LOCAL_TMP_DIR, filename)
try:
await attachment.save(local_path)
await message.channel.send(f"📥 `{filename}` を一時保存しました。GCEに転送中...")
_, err = scp_to_gce(local_path, filename)
if err:
await message.channel.send(f"❌ 転送失敗:\n```\n{err}\n```")
else:
await message.channel.send(f"✅ `{filename}` を GCE に転送しました!")
except Exception as e:
await message.channel.send(f"❌ 保存・転送エラー:\n```\n{str(e)}\n```")
elif content.startswith("!lsmod"):
cmd = f"ls {MOD_REMOTE_DIR}"
output, err = ssh_to_gce(cmd)
if err:
await message.channel.send(f"❌ mod一覧取得失敗:\n```\n{err}\n```")
elif output.strip() == "":
await message.channel.send("📁 modディレクトリは空です。")
else:
await message.channel.send(f"📄 mod一覧:\n```\n{output}\n```")
elif content.startswith("!rmmod"):
parts = message.content.split(maxsplit=1)
if len(parts) != 2:
await message.channel.send("⚠️ 使用法: `!rmmod <ファイル名>`")
return
filename = parts[1]
cmd = f"rm {MOD_REMOTE_DIR}/{filename}"
_, err = ssh_to_gce(cmd)
if err:
await message.channel.send(f"❌ 削除失敗:\n```\n{err}\n```")
else:
await message.channel.send(f"🗑️ `{filename}` を削除しました。")
client.run(TOKEN)
このスクリプトは以下のコマンドに対応します。
・!start:GCEサーバーの起動, IPアドレスの出力
・!stop:GCEサーバーの停止
・!ip:IPアドレスの出力
・!addmod(ファイル添付と共に送信):modの追加
・!lsmod:modsファイルの中身を出力
・!rmmod [filename]:指定したファイル名のファイルをmodsディレクトリ内から削除
systemdにサービスファイルを追加し、上記スクリプトをインスタンスの起動時に自動実行するように設定していきます。
スクリプトファイルの作成
$ nano /etc/systemd/system/discord-bot.service
下記の内容を追加
[Unit]
Description=Discord Minecraft Control Bot
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /home/ec2-user/discord_bot.py
WorkingDirectory=/home/ec2-user
User=ec2-user
Restart=always
RestartSec=10
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
次に、追加したサービスファイルを登録します。
$ sudo systemctl daemon-reload
$ sudo systemctl enable discord-bot.service
$ sudo systemctl start discord-bot.service
正常に作動していることを確認します。
$ sudo systemctl status discord-bot.service
● discord-bot.service - Discord Minecraft Control Bot
Loaded: loaded (/etc/systemd/system/discord-bot.service; enabled; preset: disabled)
Active: active (running) since Mon 2025-03-31 05:57:47 UTC; 10h ago
Main PID: 127050 (python3)
Tasks: 3 (limit: 1111)
Memory: 27.6M
CPU: 27.978s
CGroup: /system.slice/discord-bot.service
└─127050 /usr/bin/python3 /home/ec2-user/discord_bot.py
6. サーバー自動停止時のメッセージ送信スクリプトの設定
GCEサーバーに戻って作業を行います。
ワールドに誰もいなかった場合に自動停止したときのメッセージ送信をするスクリプトを作成します。
先ほどと同様にpip3をインストール
$ python3 -m ensurepip --upgrade
$ pip3 --version
discord.pyのインストール
$ pip3 install -U discord.py
スクリプトファイルの作成
$ nano ~/minecraft/discord-notify.py
チャンネルIDは、botがメッセージを送るテキストチャンネルです。
テキストチャンネルを右クリックすると一番下にチャンネルIDをコピーとあるのでそれをクリック。
コピーできたら、以下の内容を記述します。
アクセストークンとチャンネルIDを変更してください
import discord
import asyncio
# Botトークン & チャンネルID
TOKEN = "<BOTACCESSTOKEN>"
CHANNEL_ID = <TEXTCHANNELID>
async def notify_shutdown():
intents = discord.Intents.default()
client = discord.Client(intents=intents)
@client.event
async def on_ready():
channel = client.get_channel(CHANNEL_ID)
if channel:
await channel.send("🛑 サーバーに誰もいなかったため、自動シャットダウンします。")
await client.close()
await client.start(TOKEN)
if __name__ == "__main__":
asyncio.run(notify_shutdown())
7. SSH接続の設定
EC2に戻ります
まず、EC2でSSH鍵ペアを作成します。YOURUSERNAMEには、GCEサーバーのユーザーIDを指定してください。
$ ssh-keygen -t rsa -f ~/.ssh/gce_key -C <YOURUSERNAME>
次に、以下によって得られる出力を間違わないようにコピーします。
$ cat ~/.ssh/gce_key.pub
gcloud CLIのssh機能を用いて接続します。 例によって、YOURUSERNAMEは適宜変更してください。尚、 YOURGCEINSTANCENAMEはGCEサーバーのインスタンス名を入力してください。
さらに、には東京リージョンならasia-northeast1-a, asia-northeast1-b, asia-northeast1-cのうち適切なものを、大阪リージョンならasia-northeast2-a, asia-northeast2-b, asia-northeast2-c, のうち適切なものを選択してください。
// テンプレート
$ gcloud compute ssh <YOURUSERNAME>@<YOURGCEINSTANCENAME> --zone <ZONE>
// 実際に使用したコマンド
$ gcloud compute ssh <MYUSERNAME>@minecraft-server --zone asia-northeast1-b
最後に、権限を付与しておきます。
$ chmod +x discord-notify.py
$ chmod +x discord-bot.service
これで、EC2インスタンスを再起動させるとdiscord_bot.pyとnotify_discord.pyが自動で起動し、DiscordサーバーでBOTがオンラインになるはずです。
動作確認
- EC2サーバーを再起動して少し待つとDiscord Botがサーバー上でオンラインになります
- !startコマンドを実行し、少し待ってからメッセージで出たIPアドレスにMinecraftで接続します
- !ipコマンドでも同様のIPアドレスが出力されることを確認します
- !addmodコマンドでファイルを送信することができることを確認します
- !lsmodコマンドでmodsディレクトリの中身のリストが出力されることを確認します
- !rmmodコマンドでmodsディレクトリ内の指定したファイルが削除されていることを確認します
- ワールドにプレイヤーがいる場合といない場合で10分毎の動作の確認を行います
- プレイヤーがいない場合
コンソールにプレイヤーがいない旨の出力と自動でシャットダウンがされることを確認します - プレイヤーがいる場合
勝手にサーバーが停止しないことを確認します
- プレイヤーがいない場合
- 1時間毎にバックアップが取られていることを確認します
ディレクトリはGCEサーバー内で ~/minecraft/backups 内に生成されます - !stopコマンドでサーバーが停止することを確認します
これらすべての動作が確認できれば成功です!!お疲れさまでした!!!!
感想
もっと簡潔に書けたと思うなぁ。あと絶対どこか間違えてる。w