【GCP】使用GCE搭建的Java版Minecraft服务器(Spigot),升级版本(1.18.2→1.19)【Java版Minecraft】
简述
在GCP上升级Minecraft服务器(Spigot)的版本(1.18.2→1.19)。
翻译成中文后,只需要一种版本:
目标
- 新要素で遊びたいよね!
预先调查
Spigot
1.19更新来てたので、バージョンアップ敢行。
建立
由于这次只是更新已经运行中的Minecraft服务器的AP部分,所以没有考虑进行代码化,而是选择了手动更新。
1.1. 水龙头更新
先试试更新”BuildTools.jar”并以”latest”指定进行构建。
wget -P /opt/minecraft https://hub.spigotmc.org/jenkins/job/BuildTools/lastStableBuild/artifact/target/BuildTools.jar
java -jar BuildTools.jar --rev latest
然而,最终完成的版本是1.18.2(´・ω・`)。然而,有关其已发布的信息,所以明确指定为1.19。
java -jar BuildTools.jar --rev 1.19
一切順利,已经成功构建了“spigot-1.19.jar”。
1.2. 插件的主要更新
因为没有思考就替换了水龙头并重新启动,结果插件出现了各种错误(嗯,也是这样啊),所以同时进行了以下更新。
噴泉水龍頭更新(將舊文件重新命名並使用wget下載最新版本)
wget https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/master/lastSuccessfulBuild/artifact/bootstrap/spigot/target/Geyser-Spigot.jar
水门阀更新(通过对旧文件进行重命名并使用wget下载最新版本)
wget https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/master/lastSuccessfulBuild/artifact/spigot/target/floodgate-spigot.jar
另外,据说还需要将`chunk`更新到最新版本,首次启动时需要使用以下命令。
java -Xms1024M -Xmx8G -jar spigot.jar --nogui --forceUpgrade
到目前为止,我们终于成功连接到了1.19版本的整合版和Java版客户端。
他的插件出了一些错误,但这些错误还没有进行版本更新,所以得等一下。
特别是可以通过网络浏览器查看地图的“Dynmap”非常方便,所以希望它能尽快与1.19版本兼容…。
2. 其他附加事项
有些代码和脚本需要修正,这是备忘录。
2.1. 修正备份脚本
以前,我在GCE的「metadata_shutdown_script」中执行了停止Minecraft服务器和备份世界数据的操作。但是仔细一看,备份并没有成功…(只有在手动执行脚本时才成功备份)我发现,当使用「metadata_shutdown_script」来执行命令或脚本时,它不会等待它们完成,所以在关闭序列中无法处理无法结束的操作。
因此,我们需要更改备份脚本的处理时机。
基本上,停止Minecraft服务器是通过监视Discord消息并执行GCE停止命令来实现的,因此我们决定在该停止命令之前执行备份脚本。
另外,我们发现“metadata_shutdown_script”不可靠,所以我们决定不再使用它,而是将所有命令集中到备份脚本中。
以下是备份脚本。
#!/bin/bash## 变量定义
WORLDS=(hoge hoge_nether hoge_the_end hoge2 hoge2_nether hoge3 hoge3_nether hoge4)
BACKUP_BUCKET=’gs://hoge/’
## 游戏内发送关闭通知
screen -p 0 -S tskserver -X eval ‘stuff “say 30秒后服务器将关闭,请登出\015″‘
sleep 10
screen -p 0 -S tskserver -X eval ‘stuff “say 20秒后服务器将关闭,请登出\015″‘
sleep 10
screen -p 0 -S tskserver -X eval ‘stuff “say 10秒后服务器将关闭,请登出\015″‘
sleep 5
screen -p 0 -S tskserver -X eval ‘stuff “say 5秒后服务器将关闭,请登出\015″‘
sleep 5
## 保存并停止
screen -p 0 -S tskserver -X eval ‘stuff ‘save-all’\\015’
screen -p 0 -S tskserver -X eval ‘stuff ‘stop’\\015’
# 备份目录清空
rm -r ${BASH_SOURCE%/*}/backup_worlds/*
for world in ${WORLDS[@]}; do
cp -r ${BASH_SOURCE%/*}/${world} ${BASH_SOURCE%/*}/backup_worlds/
done
/usr/bin/zip -r ${BASH_SOURCE%/*}/backup_worlds.zip ${BASH_SOURCE%/*}/backup_worlds
/snap/bin/gsutil cp -R ${BASH_SOURCE%/*}/backup_worlds.zip ${BACKUP_BUCKET}
/bin/rm ${BASH_SOURCE%/*}/backup_worlds.zip
2.2. DiscordBot脚本修改
我实际运行了上述备份脚本,并修复了DiscordBot脚本。

以下是DiscordBot脚本。
# インストールした discord.py を読み込む
from discord.ext import commands
import discord
from discord_buttons_plugin import *
from discord.utils import get
from dislash import InteractionClient, SelectMenu, SelectOption
import os
import time
import subprocess
from subprocess import PIPE
import requests
import aiohttp# 変数定義
TOKEN = ‘hoge’ # Discord Bot のトークン
SERVICE_ACCOUNT = ‘hoge’ # gclud コマンド用サービスアカウント
INSTANCE_NAME = ‘hoge’ # Minecraftサーバー名(GCP上のVM名)
PROJECT_NAME = ‘hoge’ # Minecraftサーバーが属している GCP Project
HOST_PROJECT_NAME = ‘hoge’ # DNS を管理する GCP Project
ZONE_NAME = ‘asia-northeast1-b’ # Minecraftサーバーが属している GCP ゾーン
DNS_ZONE = ‘hoge’
RECORD_NAME = ‘hoge’
RECORD_TTL = ‘300’
RECORD_TYPE = ‘A’
MCS_PORT = ‘hoge’ # Minecraftサーバーポート
CH_ID = hoge # Bot が発言する Discord のチャンネルID
MY_BOT_NAME = ‘hoge’ # 上記チャンネルの Webhook URL から発言した際の Bot の名前
WEB_HOOK_URL = ‘hoge’
MONITOR_CH_IDS = [hoge, hage] # 入退室を監視する対象のボイスチャンネル(チャンネルIDを指定)
Intents = discord.Intents.default()
Intents.members = True
bot = commands.Bot(command_prefix = “!”, intents=Intents)
buttons = ButtonsClient(bot)
slash = InteractionClient(bot)
async def notify_callback(id, token):
url = “https://discord.com/api/v8/interactions/{0}/{1}/callback”.format(id, token)
json = {
“type”: 6
}
async with aiohttp.ClientSession() as s:
async with s.post(url, json=json) as r:
if 200 <= r.status < 300:
return
# 起動時に動作する処理
@bot.event
async def on_ready():
ch = bot.get_channel(CH_ID)
#await ch.send(‘Minecraft Administrator Bot 起動’)
#await ch.send(‘/mcs status’)
# 起動したらターミナルにログイン通知が表示される
#print(‘ログインなう’)
#print(‘/mcs help でコマンドの確認ができるよ’)
# debug
#command = [‘gcloud’, f’–account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(Status)”‘]
#instance_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(Status)”‘], shell=True)
#print(str(instance_status))
await buttons.send(
content=”Minecraft Administrator Bot 起動”,
channel = CH_ID,
components = [
ActionRow([
Button(
style = ButtonType().Primary,
label = “Start(MCS起動) “,
custom_id = “start”,
),
Button(
style = ButtonType().Secondary,
label = “Stop(MCS停止) “,
custom_id = “stop”
),
Button(
style = ButtonType().Success,
label = “Status(MCS確認)”,
custom_id = “status”,
),
Button(
style = ButtonType().Link,
label = “マップ集 “,
url=”http://mcs.hiko.games/”,
)
])
]
)
@buttons.click
async def start(ctx): #関数名は上で指定したカスタムidに対応している
# fail to interaction 対策
await notify_callback(ctx.id, ctx.token)
# debug
#await ctx.reply(“Startボタンが押されました”, flags = MessageFlags().EPHEMERAL) #ボタンを押した人のみ見えるメッセージ
#await ctx.channel.send(“Startボタンが押されました”) #誰でも見れる
#サーバーの起動
await ctx.channel.send(‘Minecraftサーバーを起動開始’)
await ctx.channel.send(‘※「起動完了」が表示されるまで他のコマンドを実行させないこと※’)
await ctx.channel.send(‘Minecraftサーバーを起動中……’)
subprocess.run([f’gcloud –account={SERVICE_ACCOUNT} compute instances start {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME}’], shell=True)
instance_status = “”
while ‘RUNNING’ in str(instance_status):
time.sleep(5)
instance_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(Status)”‘], shell=True)
#print(str(instance_status))
# MinecraftサーバーのGIP取得
instance_gip = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(networkInterfaces.accessConfigs.natIP)”‘], shell=True)
instance_gip = str(instance_gip)[4:-5]
#print(instance_gip)
# Debug
#instance_gip = ‘127.0.0.1’
#print(instance_gip)
# DNS Record の状態取得
dns_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} dns record-sets list –zone={DNS_ZONE} –name={RECORD_NAME} –project {HOST_PROJECT_NAME} –format=”value(DATA)”‘], shell=True)
dns_status = str(dns_status)[2:-3]
#print(dns_status)
# DNS Record 書き換え
subprocess.run([f’gcloud beta dns record-sets transaction start –zone={DNS_ZONE} –account={SERVICE_ACCOUNT} –project={HOST_PROJECT_NAME}’], shell=True)
subprocess.run([f’gcloud beta dns record-sets transaction remove {dns_status} –name={RECORD_NAME} –ttl={RECORD_TTL} –type={RECORD_TYPE} –zone={DNS_ZONE} –account={SERVICE_ACCOUNT} –project={HOST_PROJECT_NAME}’], shell=True)
subprocess.run([f’gcloud beta dns record-sets transaction add {instance_gip} –name={RECORD_NAME} –ttl={RECORD_TTL} –type={RECORD_TYPE} –zone={DNS_ZONE} –account={SERVICE_ACCOUNT} –project={HOST_PROJECT_NAME}’], shell=True)
subprocess.run([f’gcloud beta dns record-sets transaction execute –zone={DNS_ZONE} –account={SERVICE_ACCOUNT} –project={HOST_PROJECT_NAME}’], shell=True)
# 確認
dns_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} dns record-sets list –zone={DNS_ZONE} –name={RECORD_NAME} –project {HOST_PROJECT_NAME} –format=”value(DATA)”‘], shell=True)
#print(str(dns_status)[2:-3])
await ctx.channel.send(‘……Minecraftサーバーを起動完了’)
@buttons.click
async def stop(ctx): #関数名は上で指定したカスタムidに対応している
# fail to interaction 対策
await notify_callback(ctx.id, ctx.token)
# debug
#await ctx.reply(“Stopボタンが押されました”, flags = MessageFlags().EPHEMERAL) #ボタンを押した人のみ見えるメッセージ
#await ctx.channel.send(“Stopボタンが押されました”) #誰でも見れる
#サーバーの停止処理開始
await ctx.channel.send(‘Minecraftサーバーを停止開始’)
await ctx.channel.send(‘※「停止完了」が表示されるまで他のコマンドを実行させないこと※’)
await ctx.channel.send(‘Minecraftサーバーを停止中……’)
#ワールドデータのバックアップスクリプト実行
result = subprocess.run([f’ssh -o StrictHostKeyChecking=no -i /opt/discordbot/mcs_key-dev.pem sshadmin@mcs.hiko.games sudo -u root bash /opt/minecraft/mcs_backup.sh’], shell=True)
if result.returncode == 0:
await ctx.channel.send(‘ワールドデータのバックアップが正常終了’)
else:
await ctx.channel.send(‘ワールドデータのバックアップが異常終了’)
#サーバー停止実行
subprocess.run([f’gcloud –account={SERVICE_ACCOUNT} compute instances stop {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME}’], shell=True)
instance_status = “”
while ‘TERMINATED’ in str(instance_status):
time.sleep(5)
instance_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(Status)”‘], shell=True)
#print(str(instance_status))
await ctx.channel.send(‘……Minecraftサーバーを停止完了’)
@buttons.click
async def status(ctx): #関数名は上で指定したカスタムidに対応している
# fail to interaction 対策
await notify_callback(ctx.id, ctx.token)
# debug
#await ctx.reply(“Statusボタンが押されました”, flags = MessageFlags().EPHEMERAL) #ボタンを押した人のみ見えるメッセージ
#await ctx.channel.send(“Statusボタンが押されました”) #誰でも見れる
#サーバーの状態確認
await ctx.channel.send(‘Minecraftサーバの状態確認中……’)
instance_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(Status)”‘], shell=True)
#print(str(instance_status))
if ‘RUNNING’ in str(instance_status):
await ctx.channel.send(‘……Minecraftサーバ稼働中’)
elif ‘TERMINATED’ in str(instance_status):
await ctx.channel.send(‘……Minecraftサーバ停止中’)
else:
await ctx.channel.send(‘……Minecraftサーバの状態不明’)
@bot.event
# チャンネルへの入室ステータスが変更されたときの処理
#@bot.event
async def on_voice_state_update(member, before, after):
## debug
#ch = client.get_channel(CH_ID)
#await ch.send(‘debug 用メッセージ’)
#
#main_content = {
#”content”: “debug 用メッセージ”
#}
#requests.post(WEB_HOOK_URL,main_content)
# チャンネルへの入室ステータスが変更されたとき(ミュートON、OFFに反応しないように分岐)
if before.channel != after.channel:
### MinecraftAdminBotではなくHGL Bot に発言させるため、Webhook に投げる ###
# 退室通知
if before.channel is not None and before.channel.id in MONITOR_CH_IDS:
main_content = {
“content”: “**” + before.channel.name + “** から、__” + member.name + “__ が退室なう”
}
requests.post(WEB_HOOK_URL,main_content)
# 入室通知
if after.channel is not None and after.channel.id in MONITOR_CH_IDS:
main_content = {
“content”: “**” + after.channel.name + “** に、__” + member.name + “__ が入室なう”
}
requests.post(WEB_HOOK_URL,main_content)
@bot.event
async def on_message(message):
#debug
#await message.channel.send(‘[debug]メッセージイベントは拾ってるで’)
#test01 = message.author.bot
#await message.channel.send(test01)
#メッセージ送信者がBotだった場合は処理しない、ただし特定のBotのみ処理を継続する
if message.author.bot == True:
author = message.author
if f'{author}’ == f'{MY_BOT_NAME}’:
#await message.channel.send(‘[debug]{MY_BOT_NAME}やで’)
pass
else:
#await message.channel.send(‘[debug]botやで’)
return
else:
pass
#サーバーの起動
if message.content == ‘/mcs start’:
await message.channel.send(‘Minecraftサーバーを起動開始’)
await message.channel.send(‘※「起動完了」が表示されるまで他のコマンドを実行させないこと※’)
await message.channel.send(‘Minecraftサーバーを起動中……’)
subprocess.run([f’gcloud –account={SERVICE_ACCOUNT} compute instances start {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME}’], shell=True)
instance_status = “”
while ‘RUNNING’ in str(instance_status):
time.sleep(5)
instance_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(Status)”‘], shell=True)
print(str(instance_status))
# MinecraftサーバーのGIP取得
instance_gip = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(networkInterfaces.accessConfigs.natIP)”‘], shell=True)
instance_gip = str(instance_gip)[4:-5]
print(instance_gip)
# Debug
#instance_gip = ‘127.0.0.1’
#print(instance_gip)
# DNS Record の状態取得
dns_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} dns record-sets list –zone={DNS_ZONE} –name={RECORD_NAME} –project {HOST_PROJECT_NAME} –format=”value(DATA)”‘], shell=True)
dns_status = str(dns_status)[2:-3]
print(dns_status)
# DNS Record 書き換え
subprocess.run([f’gcloud beta dns record-sets transaction start –zone={DNS_ZONE} –account={SERVICE_ACCOUNT} –project={HOST_PROJECT_NAME}’], shell=True)
subprocess.run([f’gcloud beta dns record-sets transaction remove {dns_status} –name={RECORD_NAME} –ttl={RECORD_TTL} –type={RECORD_TYPE} –zone={DNS_ZONE} –account={SERVICE_ACCOUNT} –project={HOST_PROJECT_NAME}’], shell=True)
subprocess.run([f’gcloud beta dns record-sets transaction add {instance_gip} –name={RECORD_NAME} –ttl={RECORD_TTL} –type={RECORD_TYPE} –zone={DNS_ZONE} –account={SERVICE_ACCOUNT} –project={HOST_PROJECT_NAME}’], shell=True)
subprocess.run([f’gcloud beta dns record-sets transaction execute –zone={DNS_ZONE} –account={SERVICE_ACCOUNT} –project={HOST_PROJECT_NAME}’], shell=True)
# 確認
dns_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} dns record-sets list –zone={DNS_ZONE} –name={RECORD_NAME} –project {HOST_PROJECT_NAME} –format=”value(DATA)”‘], shell=True)
print(str(dns_status)[2:-3])
await message.channel.send(‘……Minecraftサーバーを起動完了’)
#サーバーの停止
if message.content == ‘/mcs stop’:
#サーバーの停止処理開始
await message.channel.send(‘Minecraftサーバーを停止開始’)
await message.channel.send(‘※「停止完了」が表示されるまで他のコマンドを実行させないこと※’)
await message.channel.send(‘Minecraftサーバーを停止中……’)
#ワールドデータのバックアップスクリプト実行
result = subprocess.run([f’ssh -o StrictHostKeyChecking=no -i /opt/discordbot/mcs_key-dev.pem sshadmin@mcs.hiko.games sudo -u root bash /opt/minecraft/mcs_backup.sh’], shell=True)
if result.returncode == 0:
await message.channel.send(‘ワールドデータのバックアップが正常終了’)
else:
await message.channel.send(‘ワールドデータのバックアップが異常終了’)
#サーバー停止実行
subprocess.run([f’gcloud –account={SERVICE_ACCOUNT} compute instances stop {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME}’], shell=True)
instance_status = “”
while ‘TERMINATED’ in str(instance_status):
time.sleep(5)
instance_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(Status)”‘], shell=True)
print(str(instance_status))
await message.channel.send(‘……Minecraftサーバーを停止完了’)
#ヘルプの表示
if message.content == ‘/mcs help’:
await message.channel.send(‘/mcs start : Minecraftサーバの起動’)
await message.channel.send(‘/mcs stop : Minecraftサーバの停止’)
await message.channel.send(‘/mcs status : Minecraftサーバの状態確認’)
#サーバーの状態確認
if message.content == ‘/mcs status’:
await message.channel.send(‘Minecraftサーバの状態確認中……’)
instance_status = subprocess.check_output([f’gcloud –account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} –project {PROJECT_NAME} –zone {ZONE_NAME} –format=”value(Status)”‘], shell=True)
print(str(instance_status))
if ‘RUNNING’ in str(instance_status):
await message.channel.send(‘……Minecraftサーバ稼働中’)
elif ‘TERMINATED’ in str(instance_status):
await message.channel.send(‘……Minecraftサーバ停止中’)
else:
await message.channel.send(‘……Minecraftサーバの状態不明’)
bot.run(TOKEN)
截取停止处理部分
#サーバーの停止
if message.content == '/mcs stop':
#サーバーの停止処理開始
await message.channel.send('Minecraftサーバーを停止開始')
await message.channel.send('※「停止完了」が表示されるまで他のコマンドを実行させないこと※')
await message.channel.send('Minecraftサーバーを停止中……')
#ワールドデータのバックアップスクリプト実行
result = subprocess.run([f'ssh -o StrictHostKeyChecking=no -i /opt/discordbot/mcs_key-dev.pem hoge@hoge sudo -u root bash /opt/minecraft/mcs_backup.sh'], shell=True)
if result.returncode == 0:
await message.channel.send('ワールドデータのバックアップが正常終了')
else:
await message.channel.send('ワールドデータのバックアップが異常終了')
#サーバー停止実行
subprocess.run([f'gcloud --account={SERVICE_ACCOUNT} compute instances stop {INSTANCE_NAME} --project {PROJECT_NAME} --zone {ZONE_NAME}'], shell=True)
instance_status = ""
while 'TERMINATED' in str(instance_status):
time.sleep(5)
instance_status = subprocess.check_output([f'gcloud --account={SERVICE_ACCOUNT} compute instances describe {INSTANCE_NAME} --project {PROJECT_NAME} --zone {ZONE_NAME} --format="value(Status)"'], shell=True)
print(str(instance_status))
await message.channel.send('……Minecraftサーバーを停止完了')
我通过SSH登录到Minecraft服务器并执行了备份脚本,等待其完成后修改为停止GCE。目前看来,一切运行良好,感到宽慰。
3. 总结
(Translated from Japanese: 总结)
因为这次的版本更新只是“让它先能够运作起来”,所以我还完全无法进行游戏,接下来计划进行一些确认工作。