• 資源エンドのリセットを、毎回手でやるのが面倒
  • でも定期で回すと、再生成の途中で誰かが入ってきそう
  • 失敗したときにサーバーが変な状態で残るのも怖い

資源用のエンドは、遊ばれるほど掘られて、資源が減っていきます。

僕のサーバーにもリセット用のスクリプトはあったものの、流すのは毎回手動。

これを毎週日曜の朝4時に自動で回すことにしました。そのとき引っかかったのが、再生成の途中で入ってくる人をどう防ぐか、という点です。

選んだのは、リセット中だけマイクラ標準のホワイトリストを有効にして、全員を締め出すやり方でした。

この記事で扱うのは次の3つです。

  • 再生成のあいだ全員を締め出す組み方
  • 途中で失敗しても締め出しを解除する仕組み
  • systemdでバックアップのあとに続けて動かす設定

自分のサーバーで定期リセットを組むとき、どこで事故が起きるかを判断しやすくなるでしょう。

目次

前提の環境

項目内容
サーバーPaper 26.2(Dockerのitzg/minecraft-serverで動かしている)
資源エンドMultiverse-Core 5.8.1のワールドresource_the_end
入場の判定自作のDiscord認証プラグインMCAuth。標準のホワイトリストは使っていない
定期実行ホストのUbuntuのsystemdタイマー
操作シェルスクリプトからRCON(外部からサーバーコマンドを送る仕組み)で実行

資源エンドへは、ロビーの感圧板から入れるようにしています。

手動のリセットでやっていたこと

もともとのreset-end.shは、次の順で動いていました。

  1. サーバーを止めてバックアップを取り、再開する
  2. 接続者がいれば、30秒前にチャットで告知する
  3. 資源エンドにいる人を、ロビーのスポーン地点へテレポートする
  4. mv regen resource_the_end --seedで新しいシードに作り直し、mv confirmで確定する
  5. ワールドのUIDが変わったことで、作り直しが終わったと判断する
  6. メイン島の地表を調べて、mv setspawnでスポーン地点を決める

3でロビーへ移しているのは、mv regenの--remove-playersを使わないためです。このオプションは、再生成のあとにプレイヤーを同じワールドのスポーンへ戻します。ところが作り直した直後のスポーンは出口ポータルの上で、そこへ戻されると困るわけです。

手動なら、流す人がその場で様子を見られます。困るのは、誰も見ていない時間に自動で回すとき。

定期にすると、途中で入ってくる人を止められない

自動で回すと、3でエンドを空にしたあと、4の再生成が終わるまでのあいだに、誰かがロビーの感圧板を踏むかもしれません。

再生成中のワールドにテレポートされると、何が起きるか分かりません。少なくとも、出口ポータルの上に立たされるのは避けたいところです。

止め方の候補は2つ。

方法良いところ気になるところ
資源エンドへの移動だけを止めるほかのワールドで遊んでいる人に影響しない感圧板、コマンド、ポータルなど、入口を全部ふさぐ必要がある
サーバー全体を締め出す入口がどこにあっても関係ない朝4時の数分間、全員が入れなくなる

朝4時の数分間なので、サーバー全体を締め出すほうにしました。入口を全部ふさげているかを考えなくて済むのが大きいです。

リセット中だけホワイトリストを有効にする

資源エンドの定期リセットの流れ。日曜4時のバックアップに成功したら、30秒前に告知し、ホワイトリストを有効にして、エンドにいる人をロビーへ移し、全員を切断する。再生成とスポーン設定のあとにホワイトリストを無効に戻す。失敗してもtrapで無効に戻り、コンテナが再起動しても起動時に無効へ戻る

締め出しには、マイクラ標準のホワイトリストを使いました。

僕のサーバーは、入場の判定を自作のMCAuthだけで行っています。標準のホワイトリストはComposeのENABLE_WHITELIST: "FALSE"で切ってあり、whitelist.jsonも空のまま。

この状態でRCONからwhitelist onにすると、whitelist.jsonに誰もいないので、OP以外は誰も入れなくなります。リスト作りも後片付けもいりません。

MCAuthとぶつからないかも確かめました。MCAuthは、ログイン直前のイベントで未認証の人を拒否するだけで、許可を上書きする処理は持っていません。そのため、MCAuthを通った人も、そのあとのホワイトリストの判定で止まる仕組みです。

告知のあとの流れは、次のとおりです。

rcon "say 30秒後に資源エンドをリセットします。リセット中の数分間はサーバーに入れません。"
sleep 30

whitelist_enabled=1
[[ "$(rcon "minecraft:whitelist on")" == *"turned on"* ]] || fail "ホワイトリストを有効にできませんでした。"

# 資源エンドにいる人は、先にロビーへ移してから切断する
rcon "minecraft:execute in minecraft:resource_the_end as @a[distance=0..] in minecraft:overworld run minecraft:tp @s $lobby_spawn"

rcon "minecraft:kick @a 資源エンドをリセットしています。数分後に入り直してください。"

切断の前にロビーへ移すのは、マイクラが切断した時点の位置を保存するからです。エンドにいたまま切断すると、入り直したときに、作り直したエンドの同じ座標に出てしまいます。

切断したあとは、listで接続者が0人になったのを確かめてから再生成へ進みます。0人にならなければ、リセットは中止。

RCONのwhitelistやkickには、頭にminecraft:を付けました。プラグインが同じ名前のコマンドを上書きしていても、バニラのコマンドを確実に呼べます。

成功したかどうかは、RCONの返事の文言で見ています。有効にするとWhitelist is now turned on、すでに有効ならWhitelist is already turned onが返るので、turned onを含むかで判定すれば十分でしょう。

失敗しても戻し忘れない

この方法でいちばん怖いのは、途中で失敗して、ホワイトリストが有効なまま残ることです。そうなると、朝起きたら誰も入れないサーバーができあがっています。

対策は2段にしました。

1つ目は、スクリプトの終了時に必ず戻す処理です。bashのtrap(終了時に決まった処理を走らせる仕組み)で、成功でも失敗でもwhitelist offを送ります。

disable_whitelist() {
  if [[ "$whitelist_enabled" -eq 1 ]]; then
    if [[ "$(rcon "minecraft:whitelist off")" == *"turned off"* ]]; then
      whitelist_enabled=0
    else
      log "ERROR: ホワイトリストを無効に戻せませんでした。"
      return 1
    fi
  fi
}

cleanup() {
  local exit_code=$?
  trap - EXIT
  disable_whitelist || exit_code=1
  exit "$exit_code"
}

trap cleanup EXIT

2つ目は、コンテナごと落ちた場合。このときはtrapも動きません。

ここは手元のサーバーで試しました。whitelist onにしたままコンテナを再起動したところ、起動後のserver.propertiesはwhite-list=falseに戻っていました。起動時に、ComposeのENABLE_WHITELIST: "FALSE"がserver.propertiesへ反映されるからです。

スクリプトが失敗しても、コンテナが落ちても、ホワイトリストは無効に戻る。これで朝の締め出し事故はかなり起きにくくなりました。

systemdで、バックアップに成功した日だけリセットする

リセットは、日曜の朝4時のバックアップのすぐあとに動かすことにしました。バックアップのためにサーバーは一度止まるので、ついでにリセットも済ませれば、止まる回数が1回で済みます。

リセット用のタイマーを別に作り、バックアップのタイマーと同じ4時に起動する形もあり得ます。ただ、2つのタイマーが同時に発火すると、どちらが先に動くかがはっきりしません。

そこで、日曜だけは1つのサービスで、バックアップとリセットを順番に動かすことにしました。

[Service]
Type=oneshot
# 毎日のバックアップと同じ処理を行い、成功した場合だけ資源エンドをリセットする。
ExecStart=/usr/bin/env bash /home/ubuntu/spsmc-infra/scripts/stop-backup.sh --update-geyser
ExecStart=/usr/bin/env bash /home/ubuntu/spsmc-infra/scripts/reset-end.sh --skip-backup

Type=oneshotのサービスにExecStartを2行書くと、上から順に実行されます。1行目が失敗したら、2行目は動きません。バックアップが取れなかった日にリセットだけ進む、という事故はこれで防げるでしょう。

リセット側はバックアップを済ませたあとなので、--skip-backupを付けて二重に取らないようにしました。

タイマーの分け方は次のとおりです。

タイマー起動する日時動かすもの
毎日のバックアップ月曜から土曜の4時バックアップだけ
毎週のリセット日曜の4時バックアップ、続けてリセット
# 毎日のバックアップ
OnCalendar=Mon..Sat *-*-* 04:00:00 Asia/Tokyo

# 毎週のリセット
OnCalendar=Sun *-*-* 04:00:00 Asia/Tokyo

日曜のバックアップは毎週のリセットのほうで取るので、毎日のバックアップは日曜に動かしません。

締め出しても残る穴

ホワイトリストには例外が1つあります。OPは、whitelist.jsonにいなくても入れてしまう点です。

リセット中にOPが入ってくると、締め出しは素通り。今のスクリプトでは、ここは止めていません。

もう1つ、入り直そうとした人に表示されるのは、標準の「You are not whitelisted on this server!」です。プレイヤーには少し分かりにくい文言ですが、今は標準のままにしています。

プレイヤー向けには、毎週日曜の4時にリセットすることと、そのあいだは入れないことを、公式サイトのニュースで案内します。初回は10月11日の朝4時。

まとめ:定期リセットは「止める」と「戻す」をセットで組む

定期リセットで気をつけるのは、再生成そのものより、その前後の締め出しと後片付けでした。

自分のサーバーで組むなら、次の点を見ておくと事故を防げます。

  • 再生成のあいだ、誰も入れない状態になっているか
  • スクリプトが失敗しても、コンテナが落ちても、締め出しが解除されるか
  • バックアップに失敗した日に、リセットだけが進まないか

全部を一度に作り込む必要はありません。まずは手元のサーバーでwhitelist onにしたままコンテナを再起動して、起動後に無効へ戻るかを確かめてみてください。戻るなら、最後の安全網はもうできています。

入場の判定を自作プラグインに任せている場合は、プラグインが壊れたときの振る舞いも見ておくと安心です。

関連記事マイクラのDiscord認証プラグインで「誰でも入れる」穴を塞いだ話。フェイルクローズの直し方自作のDiscord認証プラグインを監査したら、例外や起動失敗で誰でも入れる穴がありました。AsyncPlayerPreLoginEventの例外処理、連携の上書き、退出判定、起動失敗時のロックダウンの直し方と、本番で全員に再認証してもらった手順です。