1 Minecraft bedrock server の建て方(Backup by Git)
2 スクリプト一覧(今ここ)
3 各スクリプトの詳細&引用元・参考先
要件(必要な実行環境)
lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.2 LTS
Release: 22.04
Codename: jammy
head -1 <(bash --version)
GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
tmux -V
tmux 3.2a
git --version
git version 2.34.1
必要なパッケージのインストール
sudo apt install tar unzip curl wget tmux git
以下、上から順にすべて実行してください
Minecraft 用のユーザー、サーバーを用意
ユーザー・グループ作成
New_USER=minecraft
HOME_DIR=/opt/minecraft
New_GROUP=$New_USER
Login_Shell=/bin/bash
sudo adduser --system \
--home $HOME_DIR \
--shell $Login_Shell \
--group \
$New_USER
#確認
# 新規ユーザーのID、所属グループを表示
id $New_USER
# 特定のグループの所属メンバーを確認
getent group $New_GROUP
home ディレクトリに、マインクラフトを展開
※1行ずつ実行
# 作成したユーザーにログイン
sudo -i -u $New_USER
# サーバーのアーカイブをダウンロード、展開
DOWNLOAD_URL=$(curl -H "Accept-Encoding: identity" -H "Accept-Language: en" -s -L -A "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; BEDROCK-UPDATER)" https://minecraft.net/en-us/download/server/bedrock/ | grep -o 'https://minecraft.azureedge.net/bin-linux/[^"]*')
wget $DOWNLOAD_URL -O ~/bedrock-server.zip
unzip -o bedrock*server*.zip
rm -f bedrock*server*.zip
exit
ワールドデータ・設定ファイルの保存先を作成
ディレクトリ作成
# ユーザー・グループ作成 で作ったユーザーとグループ
New_USER=minecraft
New_GROUP=$New_USER
sudo rm -r "/opt/MC_Manage"
sudo mkdir -p /opt/MC_Manage/{Properties,World_Backup}
sudo chown -R $New_USER:$New_GROUP "/opt/MC_Manage/"
sudo chmod -R 750 "/opt/MC_Manage/"
# 確認
sudo ls -laF "/opt/MC_Manage/"
サービス化、起動・停止スクリプト
sudo su -c 'cat << __EOF__ > "/etc/systemd/system/minecraft.service"
[Unit]
Description=Minecraft Service
Wants=network.target
After=network.target
[Service]
Type=forking
ProtectHome=true
ProtectSystem=full
PrivateDevices=true
NoNewPrivileges=true
InaccessibleDirectories=/root /home /sys /srv /media -/lost+found
ReadWriteDirectories=/opt/minecraft
WorkingDirectory=/opt/minecraft
ExecStart=+/usr/bin/setpriv --reuid=$(/usr/bin/id -u minecraft) --regid=$(/usr/bin/id -g minecraft) --init-groups /opt/MC_Manage/start.sh
ExecStartPost=+/usr/bin/bash /opt/MC_Manage/backup.sh
ExecReload=+/usr/bin/bash -c "/usr/bin/setpriv --reuid=minecraft --regid=minecraft --init-groups /usr/bin/env XDG_RUNTIME_DIR=/run/user/$(/usr/bin/id -u minecraft) /usr/bin/systemctl --user stop nonstop_save.timer && /usr/bin/bash /opt/MC_Manage/backup.sh"
ExecStop=+/usr/bin/setpriv --reuid=$(/usr/bin/id -u minecraft) --regid=$(/usr/bin/id -g minecraft) --init-groups /usr/bin/bash -c "/usr/bin/env XDG_RUNTIME_DIR=/run/user/$(/usr/bin/id -u minecraft) /usr/bin/systemctl --user stop nonstop_save.timer; exit 0"
ExecStop=+/usr/bin/setpriv --reuid=$(/usr/bin/id -u minecraft) --regid=$(/usr/bin/id -g minecraft) --init-groups /opt/MC_Manage/stop.sh
TimeoutStartSec=20
TimeoutStopSec=600
Restart=on-failure
[Install]
WantedBy=multi-user.target
__EOF__'
sudo su -c "cat << '__EOF__' > \"/opt/MC_Manage/start.sh\"
"'#!/bin/bash''
if [ ! -v MC_EnvCheck_INIT ] && [ -f "/opt/MC_Manage/mc_env.bash" ]; then
. "/opt/MC_Manage/mc_env.bash"
fi
# Minecraft Server start
/usr/bin/tmux new-session -s minecraft -d
/usr/bin/tmux pipe-pane -t minecraft "cat >$Mine_DIR/Command.log"
/usr/bin/tmux send -t minecraft "LD_LIBRARY_PATH=$Mine_DIR ./bedrock_server | tee $Mine_DIR/Result.log" ENTER
/usr/bin/tmux send -t minecraft "gamerule showcoordinates true" ENTER
/usr/bin/tmux send -t minecraft "gamerule keepInventory true" ENTER
__EOF__'
sudo su -c "cat << '__EOF__' > \"/opt/MC_Manage/backup.sh\"
"'#!/bin/bash''
BK_INTERVAL=2h
/usr/bin/setpriv \
--reuid=$(/usr/bin/id -u minecraft) \
--regid=$(/usr/bin/id -g minecraft) \
--init-groups \
/usr/bin/env XDG_RUNTIME_DIR=/run/user/$(/usr/bin/id -u minecraft) \
/usr/bin/systemd-run \
--on-active=${BK_INTERVAL} \
--on-unit-active=${BK_INTERVAL} \
--user \
--collect \
--unit=nonstop_save.service \
/bin/bash -c "\
. /opt/MC_Manage/nonstop_save.bash ;\
HOME=/opt/minecraft mc_save BAK_SERVICE"
__EOF__'
sudo su -c "cat << '__EOF__' > \"/opt/MC_Manage/stop.sh\"
"'#!/bin/bash''
if [ ! -v MC_EnvCheck_INIT ] && [ -f "/opt/MC_Manage/mc_env.bash" ]; then
. "/opt/MC_Manage/mc_env.bash"
fi
# Minecraft Server save & terminate
if [ "`type -t mc_save`" != "function" ]; then
. "$Script_Path/nonstop_save.bash"
fi
mc_save commit unnecessary
/usr/bin/tmux send -t minecraft stop ENTER
/bin/sleep 10
/usr/bin/tmux kill-session -t minecraft
if [ "`type -t backup_worlds`" != "function" ]; then
. "$Script_Path/backup_worlds.bash"
fi
backup_worlds "Backup of stopped servers"
echo "Stopped Minecraft Server"
__EOF__'
# ls /var/lib/systemd/linger
sudo loginctl enable-linger minecraft
# 所有者の変更
sudo chown minecraft:minecraft "/opt/MC_Manage/start.sh" "/opt/MC_Manage/backup.sh" "/opt/MC_Manage/stop.sh"
# 実行権限を付与
sudo chmod 751 "/opt/MC_Manage/start.sh" "/opt/MC_Manage/backup.sh" "/opt/MC_Manage/stop.sh"
エイリアス( user:minecraft で bash -c 用)を設定
cat << '__EOF__' >> ~/.bash_aliases
function MC () {
unset Arguments
Arguments="${*//\"/\\\"}"
Arguments="${Arguments:-exec bash --login}"
sudo setpriv --reuid=$(/usr/bin/id -u minecraft) --regid=$(/usr/bin/id -g minecraft) --init-groups --reset-env bash --login -O expand_aliases -c "cd ~;IFS=' ' ${Arguments//\\\"/\"}"
}
__EOF__
. ~/.bashrc; if [ "`declare -F MC`" != "MC" ]; then
echo "describe .bash_aliases"
cat << '__EOF__' >> ~/.bashrc
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
__EOF__
fi
Minecraft Server 管理用スクリプトの用意
Git ディレクトリの作成
sudo -i -u minecraft
#設定ファイル
Properties="/opt/MC_Manage/Properties"
git init "$Properties"
cat << __EOF__ > "$Properties/.git/config"
[user]
name = Minecraft Server
email = MinecraftServer@mydomain.com
__EOF__
git -C "$Properties" config --local gc.reflogExpire "never"
git -C "$Properties" config --local gc.reflogExpireUnreachable "never"
git -C "$Properties" config --local gc.pruneExpire "1.hours.ago"
git -C "$Properties" commit --allow-empty --allow-empty-message -m ''
git -C "$Properties" commit --amend --allow-empty --allow-empty-message --no-edit
git -C "$Properties" reflog delete HEAD@{1} $(git -C "$Properties" branch --show-current)@{1}
#ワールドデータ
World_Backup="/opt/MC_Manage/World_Backup"
git init "$World_Backup"
cat << __EOF__ > "$World_Backup/.git/config"
[user]
name = Minecraft Server
email = MinecraftServer@mydomain.com
__EOF__
git -C "$World_Backup" config --local gc.reflogExpire "never"
git -C "$World_Backup" config --local gc.reflogExpireUnreachable "3.days"
git -C "$World_Backup" config --local gc.pruneExpire "1.hours.ago"
git -C "$World_Backup" commit --allow-empty --allow-empty-message -m ''
git -C "$World_Backup" commit --amend --allow-empty --allow-empty-message --no-edit
git -C "$World_Backup" reflog delete HEAD@{1} $(git -C "$World_Backup" branch --show-current)@{1}
環境変数の設定
cat << '__EOF__' >> ~/.bash_profile
# .bash_profile
. "/opt/MC_Manage/mc_env.bash"
__EOF__
cp ~/.bash_profile "/opt/MC_Manage/"
cat << '__EOF__' > "/opt/MC_Manage/mc_env.bash"
#!/bin/bash
# profile evoker
set +u
export -n MC_EnvCheck_INIT ; unset MC_EnvCheck_INIT
declare MC_EnvCheck_INIT="MC_EnvCheck_INIT"
declare -x Script_Path="/opt/MC_Manage"
# Sourcing
if [ -f "$Script_Path/variable.bash" ]; then . "$Script_Path/variable.bash"; fi
if [ -f "$Script_Path/mc_tmux.bash" ]; then . "$Script_Path/mc_tmux.bash"; fi
if [ -f "$Script_Path/nonstop_save.bash" ]; then . "$Script_Path/nonstop_save.bash"; fi
if [ -f "$Script_Path/backup_props.bash" ]; then . "$Script_Path/backup_props.bash"; fi
if [ -f "$Script_Path/backup_worlds.bash" ]; then . "$Script_Path/backup_worlds.bash"; fi
if [ -f "$Script_Path/restore_world.bash" ]; then . "$Script_Path/restore_world.bash"; fi
if [ -f "$Script_Path/git_delete.bash" ]; then . "$Script_Path/git_delete.bash"; fi
# エイリアス
alias mc_start="echo start Minecraft Server; bash $Script_Path/start.sh"
alias mc_stop="echo terminate Minecraft Server; bash $Script_Path/stop.sh"
__EOF__
cat << '__EOF__' > "/opt/MC_Manage/variable.bash"
#!/bin/bash
export Mine_DIR Worlds_DIR Script_Path Props_Bak_DIR Worlds_Bak_DIR
# 変数
Mine_DIR="/opt/minecraft"
Worlds_DIR="/opt/minecraft/worlds"
Script_Path="/opt/MC_Manage"
Props_Bak_DIR="/opt/MC_Manage/Properties"
Worlds_Bak_DIR="/opt/MC_Manage/World_Backup"
__EOF__
tmux にコマンドを送信しやすくするエイリアス
cat << '__EOF__' > "/opt/MC_Manage/mc_tmux.bash"
#!/bin/bash
if [ ! -v MC_EnvCheck_INIT ] && [ -f "/opt/MC_Manage/mc_env.bash" ]; then
. "/opt/MC_Manage/mc_env.bash"
fi
# tmux エイリアス
function mc_send () {
/usr/bin/tmux send -t minecraft "$1" ENTER
}
__EOF__
サーバーを止めないで保存するスクリプト
cat << '__EOF__' > "/opt/MC_Manage/nonstop_save.bash"
#!/bin/bash
if [ ! -v MC_EnvCheck_INIT ] && [ -f "/opt/MC_Manage/mc_env.bash" ]; then
. "/opt/MC_Manage/mc_env.bash"
fi
# Save Section
function mc_save () {
if [ "$(tmux ls -F '#{session_name}' -f '#{==:#{session_name},minecraft}' 2>/dev/null)" != "minecraft" ]; then
if [ "${1}" = "BAK_SERVICE" ]; then
XDG_RUNTIME_DIR=/run/user/$(id -u minecraft) systemctl --user stop nonstop_save.timer
exit 0
fi || true
git --git-dir="$Worlds_Bak_DIR/.git" \
--work-tree="$Worlds_DIR" \
add --intent-to-add -- :/
git --git-dir="$Worlds_Bak_DIR/.git" \
--work-tree="$Worlds_DIR" \
diff --quiet --exit-code
[ "$?" = "1" ] && backup_worlds 'Backup of stopped servers'
# 新規ファイルか更新されたファイルがあった場合は、$?=1 を示す
return 0
fi
/usr/bin/tmux send -t minecraft "save resume" ENTER
sleep 1
/usr/bin/tmux send -t minecraft "save hold" ENTER
sleep 1
EXITCODE=1 ; TRY_COUNT=0
while [ "$EXITCODE" -ne "0" ]; do
((TRY_COUNT++))
/usr/bin/tmux send -t minecraft "save query" ENTER
sleep 1
grep "Data saved. Files are now ready to be copied." <(tail -30 "$Mine_DIR/Result.log") > /dev/null 2>&1
EXITCODE=$?
if [ "$TRY_COUNT" -eq "20" ]; then break; fi
done
if [ "${1}_${2}" != "commit_unnecessary" ]; then
backup_worlds 'Save running server'
fi
/usr/bin/tmux send -t minecraft "save resume" ENTER
}
__EOF__
Git を使ってワールドデータ・設定ファイルのバックアップ
#設定ファイル
cat << '__EOF__' > "/opt/MC_Manage/backup_props.bash"
#!/bin/bash
if [ ! -v MC_EnvCheck_INIT ] && [ -f "/opt/MC_Manage/mc_env.bash" ]; then
. "/opt/MC_Manage/mc_env.bash"
fi
# エイリアス 設定ファイル関連
function backup_props () {
git --git-dir="$Props_Bak_DIR/.git" \
--work-tree="$Mine_DIR" \
add -- permissions.json server.properties allowlist.json
git -C "$Props_Bak_DIR" commit --amend --date=now --allow-empty-message -m ''"$@"
mc_delete "$Props_Bak_DIR" 10
}
function show_props_bak () {
git -C "$Props_Bak_DIR" --no-pager log --max-count=${1:-5} --reflog --format='%C(auto)%+h [%ad] %Cgreen%s' --name-only; echo ''
}
__EOF__
#ワールドデータ
cat << '__EOF__' > "/opt/MC_Manage/backup_worlds.bash"
#!/bin/bash
if [ ! -v MC_EnvCheck_INIT ] && [ -f "/opt/MC_Manage/mc_env.bash" ]; then
. "/opt/MC_Manage/mc_env.bash"
fi
# エイリアス ワールドデータ関連
function backup_worlds () {
SET_PATH="GIT_DIR=\"$Worlds_Bak_DIR/.git\" GIT_WORK_TREE=\"$Worlds_DIR\""
unset Args
while read READ; do
Args="$Args \"$READ\""
done < <(eval $SET_PATH git ls-files --others --exclude-standard :/)
eval $SET_PATH git add -- $Args >/dev/null 2>&1
eval $SET_PATH git add --update
eval $SET_PATH git update-index --remove --stdin < <(eval $SET_PATH git ls-files :/)
git -C "$Worlds_Bak_DIR" commit --amend --date=now --allow-empty-message -m ''"$@"
unset SET_PATH
mc_delete "$Worlds_Bak_DIR"
echo "Backups have been completed."
}
function show_worlds_bak () {
SKIP=0
while read LINE; do
git -C "$Worlds_Bak_DIR" log --max-count=1 --skip=$SKIP --reflog --format='%C(auto)%h [%ad] %Cgreen%s'
git -C "$Worlds_Bak_DIR" ls-tree -d --name-only $LINE
echo ''
((SKIP++))
done < <(git -C "$Worlds_Bak_DIR" log --max-count=${1:-5} --reflog --format='%C(auto)%h')
}
__EOF__
復元用スクリプト
cat << '__EOF__' > "/opt/MC_Manage/restore_world.bash"
#!/bin/bash
if [ ! -v MC_EnvCheck_INIT ] && [ -f "/opt/MC_Manage/mc_env.bash" ]; then
. "/opt/MC_Manage/mc_env.bash"
fi
# 既存のワールドを上書き
function restore_world () {
if [ ! -d "$Worlds_DIR/$2" ]; then
echo "ワールドが存在しません。正しいフォルダ名を入力してください。"
return 1
fi
unset Commit_ID
if [ "$1" = "HEAD" ] || [ "$1" = "@" ] || [ "$1" = "" ]; then
read Commit_ID < \
<(git --git-dir="$Worlds_Bak_DIR/.git" \
--work-tree="$Worlds_DIR" \
for-each-ref \
--sort=-committerdate \
--count=1 \
--format="%(objectname:short)" \
refs/heads)
fi
Commit_ID=${Commit_ID:-$1}
backup_worlds 'Backup before restore'
git --git-dir="$Worlds_Bak_DIR/.git" \
--work-tree="$Worlds_DIR" \
restore --no-overlay --worktree --source=$Commit_ID -- "$2"
}
# 別のワールドとして復元
function restore_another () {
if [ "$3" = "" ]; then
echo "復元先のフォルダ名が入力されていません。新しいフォルダ名を入力してください。"
return 1
fi
Another_Name="$3"
Another_DIR="$Worlds_DIR/$Another_Name"
if [ -d "$Another_DIR" ]; then
echo "すでにその名前のフォルダーが存在しています。別の名前で再度実行してください。"
return 1
fi
mkdir -p "$Another_DIR"
git -C "$Worlds_Bak_DIR" archive --format=tar $1 -- "$2" | tar Cx "$Another_DIR" --strip-components 1
}
__EOF__
バックアップの削除用スクリプト
cat << '__EOF__' > "/opt/MC_Manage/git_delete.bash"
#!/bin/bash
if [ ! -v MC_EnvCheck_INIT ] && [ -f "/opt/MC_Manage/mc_env.bash" ]; then
. "/opt/MC_Manage/mc_env.bash"
fi
function mc_delete () {
unset SKIP Working_DIR
SKIP=${2:-2}
Working_DIR=${1:-$Worlds_Bak_DIR}
while read COUNT_HEAD; do
git -C "$Working_DIR" reflog delete "HEAD@{$SKIP}"
done < <(git -C "$Working_DIR" --no-pager log --walk-reflogs --skip=$SKIP --format='%gd' HEAD)
while read COUNT_MAIN; do
git -C "$Working_DIR" reflog delete "$(git -C "$Working_DIR" branch --show-current)@{$SKIP}"
done < <(git -C "$Working_DIR" --no-pager log --walk-reflogs --skip=$SKIP --format='%gd' refs/heads/$(git -C "$Working_DIR" branch --show-current))
git -C "$Working_DIR" gc --quiet
}
__EOF__
最後に実行
DIR=/opt/MC_Manage ;A=0 ;if : ;then
[ -e $DIR/start.sh ] || eval 'A=1 ;echo "not exist \"start.sh\". Please create it."'
[ -e $DIR/backup.sh ] || eval 'A=1 ;echo "not exist \"backup.sh\". Please create it."'
[ -e $DIR/stop.sh ] || eval 'A=1 ;echo "not exist \"stop.sh\". Please create it."'
[ -e $DIR/mc_env.bash ] || eval 'A=1 ;echo "not exist \"mc_env.bash\". Please create it."'
[ -e $DIR/variable.bash ] || eval 'A=1 ;echo "not exist \"variable.bash\". Please create it."'
[ -e $DIR/mc_tmux.bash ] || eval 'A=1 ;echo "not exist \"mc_tmux.bash\". Please create it."'
[ -e $DIR/backup_props.bash ] || eval 'A=1 ;echo "not exist \"backup_props.bash\". Please create it."'
[ -e $DIR/backup_worlds.bash ] || eval 'A=1 ;echo "not exist \"backup_worlds.bash\". Please create it."'
[ -e $DIR/nonstop_save.bash ] || eval 'A=1 ;echo "not exist \"nonstop_save.bash\". Please create it."'
[ -e $DIR/restore_world.bash ] || eval 'A=1 ;echo "not exist \"restore_world.bash\". Please create it."'
[ -e $DIR/git_delete.bash ] || eval 'A=1 ;echo "not exist \"git_delete.bash\". Please create it."'
[ "$A" = "0" ] && echo "OK. All files exist."
fi
#改行コード
for file in /opt/MC_Manage/* ;do sed -i -e 's/\r//g' "$file" && echo "$file" ;done
#環境変数、関数の読み込み
source ~/.bash_profile
exit
#サービスの読み込み
sudo systemctl daemon-reload
#念のためタイムゾーンを東京に変更するコマンドを実行する
sudo timedatectl set-timezone Asia/Tokyo
ファイアウォールの構成
sudo apt install ufw bind9-dnsutils -y
ここから先は各自で自分に合った設定を行ってください
# sudo ufw reset && sudo ufw status
# 内側に入ってくる通信を既定で拒否
sudo ufw default deny
# LAN内の通信をすべて許可
sudo ufw allow from 192.168.0.0/16
# Tailscaleも許可
# ip -f inet -o addr show | grep tail | awk -F '[: ]' '{print $3}'
sudo ufw allow in on tailscale0
# SSHには念のため制限を掛けておく
sudo ufw limit ssh
# UFWの有効化
sudo ufw enable
sudo ufw reload
sudo ufw status verbose
Minecraft 用のファイアウォール構成ファイルを作成
sudo su -c "cat << '__EOF__' > '/etc/ufw/applications.d/Minecraft'
[Minecraft]
title=Minecraft
description=Minecraft UDP Port
ports=19132:19133/udp
__EOF__"
Minecraft Server 参加者をホワイトリスト形式で許可
↓使い方:sudo bash ~/AcceptMC.bash
Minecraft に参加する人の名前: 個々人を識別するための一意な名前を入力
Minecraft に参加する人のIPまたはホスト名: グローバルIPアドレスかホスト名を入力
※各参加者の "グローバルIPアドレス" が変わる都度、更新してください
cat << '__EOF__' > ~/AcceptMC.bash
#!/bin/bash
# 詳細を入力
printf ' Minecraft に参加する人の名前: ' ; read MC_Connect_USER ; echo $MC_Connect_USER
printf ' Minecraft に参加する人のIPまたはホスト名: ' ; read HOSTNAME_OR_IP ; echo $HOSTNAME_OR_IP
# 変数解除
unset IP_Addr MC_USER
# 古いファイアウォールの構成
MC_USER="${MC_Connect_USER:-NONE_OLD_FW}"
Old_FW=$(sudo ufw show added | awk "/Minecraft comment/"' && '"/$MC_USER/")
Old_FW="${Old_FW#'ufw '}"
# 新しいファイアウォールの構成
IP_Addr="$(dig +short $HOSTNAME_OR_IP)"
IP_Addr="${IP_Addr:-$HOSTNAME_OR_IP}"
New_FW="allow from $IP_Addr to any app Minecraft comment '$MC_USER'"
# 設定を更新
if [ "$Old_FW" != "$New_FW" ]; then
[ -n "$Old_FW" ] && eval sudo ufw delete "$Old_FW" && echo "DELETE Older FireWall"
eval sudo ufw "$New_FW" && echo "CREATE New FireWall"
else
echo "do not update."
fi
# 確認
sudo ufw status | awk '/Action/;/------/;/Minecraft/'
echo "Script Finish"
exit 0
__EOF__
単純にファイアウォールを削除する場合は
# 以下の実行結果の中から取り消したいコマンドをコピー
sudo ufw show added | sed '1d;s/^ufw//'
# 下のコマンドに続けてペースト
sudo ufw delete
以上!!!