관련 링크
서버 관리
팰월드 멀티 서버의 경우 아직 불안정한 단계이기에 사용자들의 데이터가 모종의 이유(?)로 날아가면 난감하기에 주기적으로 백업을 해주어야 한다.
백업하는 과정을 스크립트로 작성하고 마무리로 텔레그램으로 알림까지 보내는 간단한 백업용 알림 스크립트등
효과적으로 관리할 수 있는 스크립트들을 작성해보자.
팰월드 세이브 데이터 경로
Palworld의 세이브 데이터는 아래와 같은 경로에 존재한다.
ls -lh game/Pal/Saved/SaveGames/0/4D308CFCDF584AFCAB7E66F8BB5A881D/Players/
total 44K
-rw-r--r-- 1 master master 2.7K Jan 26 00:58 04637D7E000000000000000000000000.sav
-rw-r--r-- 1 master master 3.3K Jan 26 14:02 26BF7B8D000000000000000000000000.sav
-rw-r--r-- 1 master master 2.6K Jan 26 00:58 2EA7D45B000000000000000000000000.sav
-rw-r--r-- 1 master master 3.8K Jan 26 14:02 48FFF339000000000000000000000000.sav
-rw-r--r-- 1 master master 2.4K Jan 26 00:58 62E1ACE8000000000000000000000000.sav
-rw-r--r-- 1 master master 2.8K Jan 26 12:58 7FB221FA000000000000000000000000.sav
-rw-r--r-- 1 master master 3.5K Jan 26 00:58 923FD405000000000000000000000000.sav
-rw-r--r-- 1 master master 3.2K Jan 26 14:02 A15D2445000000000000000000000000.sav
-rw-r--r-- 1 master master 2.6K Jan 26 00:58 A4ABAE08000000000000000000000000.sav
-rw-r--r-- 1 master master 2.6K Jan 26 00:58 C01C1509000000000000000000000000.sav
-rw-r--r-- 1 master master 2.5K Jan 26 00:58 C57E1CD9000000000000000000000000.sav
각 플레이어들의 정보가 저장되어 있지만 누구의 세이브 데이터인지 알 수가 없게 되어있다.
유저 데이터를 쉽게 찾고 싶다면 아래 게시글을 참고하도록 하자
유저 데이터 찾기
스크립트 작성
이제부터 스크립트들을 작성해보자.
▪️ 백업 스크립트
- 유저 데이터 백업 스크립트
~/backup/script.sh
#!/usr/bin/bash
HOST='master'
SERVER_NAME="palworld-server"
if $(docker ps | grep $SERVER_NAME >/dev/null 2>&1); then
{% raw %} if [[ $(docker inspect --format '{{.State.Status }}' "$SERVER_NAME") == 'running' ]]; {% endraw %} then
echo "$SERVER_NAME is running"
# 도커 프로세스에 해당 서버가 있는 경우 & 서버가 실행 중이지 않은 경우
else
echo "$SERVER_NAME is paused"
exit 1
fi
else
echo "$SERVER_NAME is stoped"
exit 1
fi
# 압축할 디렉토리
source_dir="/home/${HOST}/palworld/game/Pal/Saved/SaveGames"
# 압축 파일이 저장될 디렉토리
backup_dir="/tmp/palworld/backup"
# 압축 파일 이름
DATE="$(date +'%Y%m%d_%H%M')"
backup_filename="palworld_backup_${DATE}.tar.gz"
# 백업 디렉토리 없는 경우 생성
if [[ ! -d "${backup_dir}" ]]; then
mkdir -p "${backup_dir}"
fi
# 로그 파일 생성
LOG_FILE_NM="/tmp/backup.log"
touch "${LOG_FILE_NM}"
{
echo -e "\n💿 PalWorld 백업 시작 ... 💿"
# 디렉토리를 gzip 압축
tar -czf "${backup_dir}/${backup_filename}" "${source_dir}" 1>/dev/null 2>&1
NAME=$(ls -lh "${backup_dir}/${backup_filename}" | awk '{print $9}')
SIZE=$(ls -lh "${backup_dir}/${backup_filename}" | awk '{print $5}')
echo "==== 백업 파일 정보 ===="
echo "파일명 : ${NAME}"
echo "파일 크기 : ${SIZE}"
echo
echo "==== 이전 백업파일 삭제 ===="
find "${backup_dir}" -type f -mmin +360 -exec sh -c "ls -l {}; rm -f {}" \;
echo -e "\n💿 PalWorld 백업 종료 ... 💿"
} > "${LOG_FILE_NM}"
# 텔레그램으로 알림 전송
TELEGRAM_FILE="/home/${HOST}/telegram/alarm.sh"
"${TELEGRAM_FILE}" "Master Server" "$(cat "${LOG_FILE_NM}")"
# 로그파일 삭제
rm -rf "${LOG_FILE_NM}"
리소스 체크 스크립트
vim resource.sh
~/resource_check/script.sh
#!/usr/bin/bash
HOST='master'
TELEGRAM_FILE="/home/${HOST}/telegram/alarm.sh"
SWP_MEMORY=$(free -h | grep 'Swap' | awk '{print $4}')
MEMORY=$(free -h | grep 'Mem' | awk '{print $4}')
LOAD_AVERAGE=$(uptime | awk -F 'load average: ' '{print $2}')
MESSAGE="
MEMORY _> ${MEMORY}
SWAP MEMORY -> ${SWP_MEMORY}
uptime -> ${LOAD_AVERAGE}"
"${TELEGRAM_FILE}" "${HOST}" "${MESSAGE}"
알림 스크립트
- 텔레그램 알림 스크립트
~/telegram/alarm.sh
#!/usr/bin/bash
if [[ ${#} -ne 2 ]]; then
clear
echo -e "Usage 👉 ${0} hostname message"
exit 1
fi
HOST='master'
TELE_INFO_FILE="/home/${HOST}/telegram/token.env"
TELE_INFOS=($(awk -F '=' '{print $2}' "${TELE_INFO_FILE}"))
TOKEN="${TELE_INFOS[0]}"
CHAT_ID="${TELE_INFOS[1]}"
URL="https://api.telegram.org/bot${TOKEN}/sendMessage"
TEXT="[${1}] ${2}"
curl -s -d "chat_id=${CHAT_ID}&text=${TEXT}" ${URL} > /dev/null
재부팅 스크립트
- 메모리 점유율 80% 이상 시 서버 재부팅
bc 패키지 설치
sudo apt-get install -y bc
~/palworld/restart.sh
#!/usr/bin/bash
HOST='master'
YAML_FILE="/home/$HOST/palworld/docker-compose.yaml"
CONTAINER_NAME="palworld-server"
THRESHOLD=80
# 변수에 각각 삽입
read total used <<< $(free -m | awk '/Mem:/ {print $2 " " $3}')
# 현재 메모리 사용량 출력
echo "Current Memory usage: $(awk '/MemTotal/{total=$2}/MemAvailable/{available=$2} END {printf "%.0f", (total-available) / total * 100}' /proc/meminfo)%"
# 메모리 사용량 계산
usedPct=$(( used * 100 / total ))
if [[ "$usedPct" -gt $THRESHOLD ]]; then
echo "Memory usage is above $THRESHOLD%."
docker exec -i ${CONTAINER_NAME} rcon-cli "Broadcast Server_will_shut_down_in_5_seconds_for_maintance_Please_log_out"
# 5초 대기
sleep 5
# 세이브
docker exec -i $CONTAINER_NAME rcon-cli save
# 서버 종료
docker exec -i $CONTAINER_NAME rcon-cli "Broadcast Server_is_shutting_down_for_maintance"
# 5초 대기
sleep 5
# 서버 재시작
docker-compose -f "${YAML_FILE}" pull
docker-compose -f "${YAML_FILE}" down
docker-compose -f "${YAML_FILE}" up -d
else
echo "Memory usage is below $THRESHOLD%."
fi
밴 유저 삭제 스크립트
- 밴 당한 유저의 세이브 데이터 자동 삭제 스크립트
~/palworld/deleteBanUserData.sh
#!/usr/bin/bash
HOST='master'
SAVE_PATH="/home/${HOST}/palworld/game/Pal/Saved/SaveGames"
echo -e "PlayerUID를 입력하세요 (여러개의 ID를 쓰는 경우 공백으로 구분)\n: "
while true; do
read -a Playeruids
if [[ -z "${Playeruids}" ]]; then
clear
echo -e "PlayerUID 미입력됨.\n"
continue
fi
break
done
clear
echo "플레이어 데이터 찾는 중 ..."
for playeruid in "${Playeruids[@]}"; do
hex=$(printf "%X\n" "$playeruid" 2>/dev/null)
if [[ "${hex}" == 0 ]]; then
echo -e "${playeruid}: invalid octal number!"
continue
fi
player_file="$(find "${SAVE_PATH}" -type f -iname "$hex*")"
if [[ -z "${player_file}" ]]; then
echo -e "해당되는 플레이어를 찾지 못했습니다."
continue
fi
echo "find! Ban Playeruid : ${hex}"
echo -e "\nIs this really the ban user's file?\n👉 ${player_file}"
read -p "y / Y " answer
while true; do
if [[ "${answer}" == 'y' ]] || [[ "${answer}" == 'Y' ]]; then
echo -e "\nDeleting ban player's data ..."
find "${SAVE_PATH}" -type f -iname "$hex*" -exec sh -c "rm -rf {}" \;
exit 0
else
echo -e "With other data ..."
break
fi
done
done
서버 체크 스크립트
- Palworld 컨테이너 alive 체크 및 서버 메모리 점유율 모니터링 결과를 디스코드 webhook으로 알림 전송
~/palworld-webhook/script.sh
#!/usr/bin/bash
#### Required variables
HOST="server01"
LOG_FILE="/home/${HOST}/discord-webhook/webhook.log"
WEBHOOK_URL_FILE="/home/${HOST}/discord-webhook/.webhook_url.txt"
DISCORD_WEBHOOK_URL=$(awk -F '=' '{print $2}' "$WEBHOOK_URL_FILE")
SERVER_NAME="palworld-server"
SERVER_ADDRESS="waytothem.store:5555"
PLAYERS="$(docker exec -i $SERVER_NAME rcon-cli ShowPlayers 2>/dev/null | awk 'NR > 1 {print}' | wc -l)"
if $(docker ps | grep $SERVER_NAME >/dev/null 2>&1); then
# 도커 프로세스에 해당 서버가 있는 경우 & 서버가 실행 중인 경우
{% raw %} if [[ $(docker inspect --format '{{.State.Status }}' "$SERVER_NAME") == 'running' ]]; {% endraw %} then
SERVER_PROCESS=' 🟢 (온라인)'
# 도커 프로세스에 해당 서버가 있는 경우 & 서버가 실행 중이지 않은 경우
else
SERVER_PROCESS=' 🟠 (중지됨)'
fi
else
SERVER_PROCESS=' 🔴 (오프라인)'
fi
#
#### 메모리 점유율 확인
MEMORY_USAGE=$(free | grep 'Mem' | awk '{print int($3/$2 * 100.0)}')
if [[ "$MEMORY_USAGE" -le 30 ]]; then
color=2003199
elif [[ "$MEMORY_USAGE" -le 50 ]]; then
color=16747520
elif [[ "$MEMORY_USAGE" -le 60 ]]; then
color=16711680
else
color=0
fi
#
#### 보낼 Embed 메시지 작성
MEMORY_MESSAGE="메모리 사용률: $MEMORY_USAGE%"
REBOOT_MESSAGE="메모리 점유율이 70% 이상 올라갈 시 자동 재부팅"
PLAYER_MESSAGE="접속 인원: \`$PLAYERS/10\`"
SERVER_STATUS_MESSAGE="서버 상태: $SERVER_PROCESS"
SERVER_ADDRESS_MESSAGE="서버 주소: \`$SERVER_ADDRESS\`"
BOUNDARY_MESSAGE="────────────────────────────"
#### JSON 데이터 인코딩
DATA=$( jq -n \
--arg bm "$BOUNDARY_MESSAGE" \
--arg mm "$MEMORY_MESSAGE" \
--arg rm "$REBOOT_MESSAGE" \
--arg pm "$PLAYER_MESSAGE" \
--arg ssm "$SERVER_STATUS_MESSAGE" \
--arg sam "$SERVER_ADDRESS_MESSAGE" \
--arg c "$color" \
'{ content: " ", embeds: [ { title: "💊 Server Status", color: $c, fields: [ { name: $bm, value: " ", inline: false }, { name: $ssm, value: " ", inline: false }, { name: $pm, value: " ", inline: false }, { name: $sam, value: " ", inline: false },{ name: $mm, value: " ", inline: false }, { name: "주의사항", value: $rm, inline: false } ] } ] }' )
# curl을 사용하여 웹훅 호출
curl -X POST -H "Content-Type: application/json" -d "$DATA" "$DISCORD_WEBHOOK_URL"
# curl의 반환 코드를 확인하여 로그 파일에 결과 기록
if [[ $? != 0 ]]; then
echo "디스코드 메시지 전송 실패!" >> "$LOG_FILE"
else
echo "디스코드 메시지 전송 성공!" >> "$LOG_FILE"
fi
함수로 분리
~/palworld/PalworldManager.sh
#!/usr/bin/bash
#### Require variables
HOST='master'
#
#### 알림 스크립트
alarm() {
if [[ ${#} -ne 2 ]]; then
clear
echo -e "Usage 👉 ${0} hostname message"
exit 1
fi
HOST='master'
TELE_INFO_FILE="/home/${HOST}/telegram/token.env"
TELE_INFOS=($(awk -F '=' '{print $2}' "${TELE_INFO_FILE}"))
TOKEN="${TELE_INFOS[0]}"
CHAT_ID="${TELE_INFOS[1]}"
URL="https://api.telegram.org/bot${TOKEN}/sendMessage"
TEXT="[${1}] ${2}"
curl -s -d "chat_id=${CHAT_ID}&text=${TEXT}" ${URL} > /dev/null
}
####
#### 백업 함수
backup() {
SERVER_NAME="palworld-server"
if $(docker ps | grep $SERVER_NAME >/dev/null 2>&1); then
{% raw %} if [[ $(docker inspect --format '{{.State.Status }}' "$SERVER_NAME") == 'running' ]]; then {% endraw %}
echo "$SERVER_NAME is running"
# 도커 프로세스에 해당 서버가 있는 경우 & 서버가 실행 중이지 않은 경우
else
echo "$SERVER_NAME is paused"
exit 1
fi
else
echo "$SERVER_NAME is stoped"
exit 1
fi
# 압축할 디렉토리
source_dir="/home/${HOST}/palworld/game/Pal/Saved/SaveGames"
# 압축 파일이 저장될 디렉토리
backup_dir="/tmp/palworld/backup"
# 압축 파일 이름
DATE="$(date +'%Y%m%d_%H%M')"
backup_filename="palworld_backup_${DATE}.tar.gz"
# 백업 디렉토리 없는 경우 생성
if [[ ! -d "${backup_dir}" ]]; then
mkdir -p "${backup_dir}"
fi
# 로그 파일 생성
LOG_FILE_NM="/tmp/backup.log"
touch "${LOG_FILE_NM}"
{
echo -e "\n💿 PalWorld 백업 시작 ... 💿"
# 디렉토리를 gzip 압축
tar -czf "${backup_dir}/${backup_filename}" "${source_dir}" 1>/dev/null 2>&1
NAME=$(ls -lh "${backup_dir}/${backup_filename}" | awk '{print $9}')
SIZE=$(ls -lh "${backup_dir}/${backup_filename}" | awk '{print $5}')
echo "==== 백업 파일 정보 ===="
echo "파일명 : ${NAME}"
echo "파일 크기 : ${SIZE}"
echo
echo "==== 이전 백업파일 삭제 ===="
find "${backup_dir}" -type f -mmin +360 -exec sh -c "ls -l {}; rm -f {}" \;
echo -e "\n💿 PalWorld 백업 종료 ... 💿"
} > "${LOG_FILE_NM}"
# 텔레그램으로 알림 전송
alarm "Master Server" "$(cat "${LOG_FILE_NM}")"
# 로그파일 삭제
rm -rf "${LOG_FILE_NM}"
}
####
#### 리소스 체크 함수
resource_check() {
SWP_MEMORY=$(free -h | grep 'Swap' | awk '{print $4}')
MEMORY=$(free -h | grep 'Mem' | awk '{print $4}')
LOAD_AVERAGE=$(uptime | awk -F 'load average: ' '{print $2}')
MESSAGE="
MEMORY _> ${MEMORY}
SWAP MEMORY -> ${SWP_MEMORY}
uptime -> ${LOAD_AVERAGE}"
alarm "${HOST}" "${MESSAGE}"
}
####
#### 재부팅 스크립트
reboot() {
YAML_FILE="/home/$HOST/palworld/docker-compose.yaml"
CONTAINER_NAME="palworld-server"
THRESHOLD=80
# 변수에 각각 삽입
read total used <<< $(free -m | awk '/Mem:/ {print $2 " " $3}')
# 현재 메모리 사용량 출력
echo "Current Memory usage: $(awk '/MemTotal/{total=$2}/MemAvailable/{available=$2} END {printf "%.0f", (total-available) / total * 100}' /proc/meminfo)%"
# 메모리 사용량 계산
usedPct=$(( used * 100 / total ))
if [[ "$usedPct" -gt $THRESHOLD ]]; then
echo "Memory usage is above $THRESHOLD%."
docker exec -i ${CONTAINER_NAME} rcon-cli "Broadcast Server_will_shut_down_in_5_seconds_for_maintance_Please_log_out"
# 5초 대기
sleep 5
# 세이브
docker exec -i $CONTAINER_NAME rcon-cli save
# 서버 종료
docker exec -i $CONTAINER_NAME rcon-cli "Broadcast Server_is_shutting_down_for_maintance"
# 5초 대기
sleep 5
# 서버 재시작
docker-compose -f "${YAML_FILE}" pull
docker-compose -f "${YAML_FILE}" down
docker-compose -f "${YAML_FILE}" up -d
else
echo "Memory usage is below $THRESHOLD%."
fi
}
####
# man logic to call functions based on passed argument
case "$1" in
backup)
backup
;;
resource)
resource_check
;;
restart)
reboot
;;
*)
echo -e "$0 {backup|resource|restart}"
exit 1
;;
esac
crontab 등록
1. 스크립트 각각 등록한 경우
# 등록된 cron job 출력
crontab -l
# 5분마다 rcon으로 조회된 서버 player 목록 파일로 생성
*/5 * * * * bash /home/master/rconUserAlarm/set-rcon-players.sh >/dev/null 2>&1
# 10분마다 재부팅 스크립트 실행
*/10 * * * * bash /home/master/palworld/restart.sh >> /home/master/backup/restart.log 2>&1
# 30분마다 백업 스크립트 실행
*/30 * * * * bash /home/master/backup/script.sh >> /home/master/backup/backup.log 2>&1
# 30분마다 리소스 체크 스크립트 실행
*/30 * * * * bash /home/master/resource_alarm/alarm.sh >> /home/master/backup/resource.log 2>&1
2. 함수 기반으로 기능 분리하여 하나의 스크립트로 등록한 경우
# 등록된 cron job 출력
crontab -l
# 5분마다 rcon으로 조회된 서버 player 목록 파일로 생성
*/5 * * * * bash /home/master/rconUserAlarm/set-rcon-players.sh >/dev/null 2>&1
# 10분마다 재부팅 스크립트 실행
*/10 * * * * bash /home/master/palworld/PalworldManager.sh restart >> /home/master/backup/restart.log 2>&1
# 30분마다 백업 스크립트 실행
*/30 * * * * bash /home/master/palworld/PalworldManager.sh backup >> /home/master/backup/backup.log 2>&1
# 30분마다 리소스 체크 스크립트 실행
*/30 * * * * bash /home/master/palworld/PalworldManager.sh resource >> /home/master/backup/resource.log 2>&1