最大32人 パルワールド Linux 専用サーバの立て方

:::note alert
現在のパルワールドは「Steam版」のみ専用サーバに接続できます。
「Xbox PC Game Pass版」は招待コードを使ったマルチプレイのみ可能で、その場合は最大4人までのプレイに限られます。
:::
:::note
公式による **[Palworld tech guide - Dedicated server guide](https://tech.palworldgame.com/dedicated-server-guide)**、**[パルワールド　JapaneseQ&A](https://docs.google.com/document/d/e/2PACX-1vTm43IoZ--_EGVCd2_tZGJNf7HzjgJmYTRkfNk8rpKWxDQgMY5Cq8_w5x6xJBpx-c7_7TIqDYSw6WRp/pub)** も合わせて参照してください。
本記事は「[方法3: Linux - SteamCMD](https://tech.palworldgame.com/dedicated-server-guide#linux)」をより詳細に説明する内容です。
:::

ゲーム内で設定できるマルチプレイでは、1 つの世界に同時に最大 4 人が参加できますが、専用サーバーをホストした場合、その数は最大 32 人になります。
（非公式ですが、設定すればそれ以上も…）

キャラクターはワールドに依存し、最初から Linux で専用サーバを建てることで、友人と 24 時間パルワールドをプレイすることができます。

よってパルワールドでは 24 時間の稼働に適した専用サーバ（Dedicated Server）プログラムが開発元からリリースされています。

この記事は Linux にある程度精通した方向けになっています。
またサーバが置かれた環境のネットワークポート開放の知識も必要です。

サーバーなんか持ってない！よくわからん！って方は、Xserver という国内サービス事業者がゲーム設定も簡単に変更できる **[Xserver パルワールドイメージ](https://game.xserver.ne.jp/support/manual/man_game_app_use_palworld.php)** を提供しているので、こちらを有償で利用するのがオススメです。

## サーバ情報
現時点のパルワールド Dedicated Server はメモリ消費が大きいです。
大人数で安定した 24 時間稼働のためには 32 または 64GB 載せましょう。

この記事は以下の環境で試しており、4人までであればこのスペックでも安定稼働しています。

| スペック | 値 | 備考 |
| ---- | ---- | ---- |
| OS | AlmaLinux 8.9 (Minimal) | Ubuntu などでも問題ありませんが、適宜コマンドを読み替えて下さい |
| CPU | 2 vCPU | 公式推奨は 4コア 以上 |
| Memory | 16 GB | 公式推奨は 16GB 以上、安定稼働のためには 32GB |
| HDD | 100 GB | 24/01 時点でサーバファイルの総容量は 4.8 GB |
| 公開ポート | 8211 (UDP) | デフォルトポート |
| SELinux | 無効 | |

VPS やミニPCなど、メモリの搭載に限りがある場合は[スワップ領域を大きめに作成する](#サーバを起動し続けるとサーバが落ちる)ことで公式推奨値を下回るメモリでもある程度は安定した稼働ができます。
ただしディスクの I/O 性能に大きく依存しますので、大きめのメモリがあれば素直に割り当てるのが望ましいです。

## 簡単な構築方法
時間をかけずにゲームサーバー構築したい人向けの方法を紹介。

### Docker
多くの Star がついている Docker リポジトリです。お好きなほうを
[![jammsen/docker-palworld-dedicated-server - GitHub](https://gh-card.dev/repos/thijsvanloef/palworld-server-docker.svg)](https://github.com/thijsvanloef/palworld-server-docker)

[![jammsen/docker-palworld-dedicated-server - GitHub](https://gh-card.dev/repos/jammsen/docker-palworld-dedicated-server.svg)](https://github.com/jammsen/docker-palworld-dedicated-server)

</div></details>

### LinuxGSM_ (Linux GameServerManagers)
ゲームサーバー管理で有名なツールです。
Install のページに従えば、わずか数行で構築が可能です。

* [Palworld | LinuxGSM_](https://linuxgsm.com/servers/pwserver/)

## 手動インストール
ここからは手動でインストールする方法です。 AlmaLinux 8.8
### 1. SELinux の無効化
SELinux を無効化します。サーバが再起動します。
```Bash
sudo sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

# 再起動して反映
reboot
```


### 2. palworld ユーザの作成
root では起動できないので、専用のユーザを作成します。
`palworld` ユーザを作成します。また sudo 権限をつけます。
```Bash
# palworld ユーザを作成
sudo useradd -m palworld

# palworld ユーザのパスワードを設定
passwd palworld

# NOPASSWD sudo を付与
echo "palworld ALL=(ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/palworld

# palworld ユーザになる
su - palworld
```
以降のコマンドは palworld ユーザで実行します。

### 3. steamcmd インストール
```Bash
# glibc.i686 libstdc++.i686 tar をインストール
sudo dnf install -y glibc.i686 libstdc++.i686 tar

# steamcmd_linux.tar.gz をダウンロードする
curl -o steamcmd_linux.tar.gz https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz

# steamcmd_linux.tar.gz の解凍
tar xf steamcmd_linux.tar.gz

# steamcmd.sh を実行
./steamcmd.sh
```
<details><summary>Ubuntu / Debian の場合</summary><div>

**Ubuntu**
```Bash
sudo add-apt-repository multiverse; sudo dpkg --add-architecture i386; sudo apt update
sudo apt install steamcmd

steamcmd
```
**Debian**
```Bash
sudo apt update; sudo apt install software-properties-common; sudo apt-add-repository non-free; sudo dpkg --add-architecture i386; sudo apt update
sudo apt install steamcmd

steamcmd
```
([SteamCMD - Valve Developer Community](https://developer.valvesoftware.com/wiki/SteamCMD#Ubuntu)より)
</div></details>

これでプロンプトが ![Monosnap RLogin - palworld@localhost_~ 2024-01-19 19.46.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/b9069d4e-df60-faa8-78c7-2dc6d882e8d0.png) になっていれば OK です。

### 4. Palworld Dedicated Server のインストール
Palworld の Dedicated Server をインストールします。
Palworld の購入とは関係なくコンピュータにダウンロードすることができます。
（もちろん接続してプレイするには Palworld の購入が必要です）
```Bash
# ユーザ認証は不要なので anonymous でログインします
> login anonymous

# /home/palworld/Steam/palworld/ をインストールディレクトリに指定します
> force_install_dir /home/palworld/Steam/palworld/

# Palworld の App ID である 2394010 を指定してインストール
> app_update 2394010 validate

# progress が 100 になるのを待つ

# steamcmd を抜ける
> quit
```

サーバーファイルは `/home/palworld/Steam/palworld/` を指定してダウンロードしていますが、`palworld` ユーザに権限のあるディレクトリであれば、どこを指定しても問題ありません。

インストールが完了し、palworld ユーザに戻っていれば OK です。
```
 Update state (0x81) verifying update, progress: 99.61 (4710917917 / 4729403737)
Success! App '2394010' fully installed.

Steam>quit
[palworld@localhost ~]$ 
```

ゲームはダウンロードできましたが、このままだとゲームが起動しません。
`steamclient.so` をダウンロードし ~/.steam/sdk64/steamclient.so に保存します。

```Bash
mkdir -p /home/palworld/.steam/sdk64/
/home/palworld/steamcmd.sh +login anonymous +app_update 1007 +quit
cp /home/palworld/Steam/steamapps/common/Steamworks\ SDK\ Redist/linux64/steamclient.so /home/palworld/.steam/sdk64/steamclient.so
```

### 5. サーバの起動確認
サーバが起動するか確認します。
```Bash
# インストールしたディレクトリに移動
cd /home/palworld/Steam/palworld

# 起動確認
./PalServer.sh
```
少しエラーがあるが、このようなメッセージになれば OK。
```
[palworld@localhost palworld]$ ./PalServer.sh 
Shutdown handler: initalize.
Increasing per-process limit of core file size to infinity.
sh: xdg-user-dir: コマンドが見つかりません
dlopen failed trying to load:
steamclient.so
with error:
steamclient.so: cannot open shared object file: No such file or directory
[S_API] SteamAPI_Init(): Loaded '/home/palworld/.steam/sdk64/steamclient.so' OK.  (First tried local 'steamclient.so')
CAppInfoCacheReadFromDiskThread took 3 milliseconds to initialize
dlmopen steamservice.so failed: steamservice.so: cannot open shared object file: No such file or directory
Setting breakpad minidump AppID = 2394010
[S_API FAIL] Tried to access Steam interface SteamUser021 before SteamAPI_Init succeeded.
[S_API FAIL] Tried to access Steam interface SteamFriends017 before SteamAPI_Init succeeded.
[S_API FAIL] Tried to access Steam interface STEAMAPPS_INTERFACE_VERSION008 before SteamAPI_Init succeeded.
[S_API FAIL] Tried to access Steam interface SteamNetworkingUtils004 before SteamAPI_Init succeeded.
```
起動ができたら、自動起動できるようにします。
Ctrl+C で一度サーバを止めましょう。

問題なく起動できることが確認できたら、常時稼働させるためにサービスとして登録します。

### 6. Systemd に登録
常時起動できるよう、systemd サービスとして登録します。
```
sudo vi /etc/systemd/system/palworld-dedicated.service
```
以下を入力して :wq で保存してください。

```
[Unit]
Description=Palworld Dedicated Server
Wants=network-online.target
After=syslog.target network.target nss-lookup.target network-online.target

[Service]
ExecStart=/home/palworld/Steam/palworld/PalServer.sh port=8211 players=32 -useperfthreads -NoAsyncLoadingThread -UseMultithreadForDS
LimitNOFILE=100000
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s INT $MAINPID
Restart=always
User=palworld
Group=palworld
TimeoutStartSec=300

[Install]
WantedBy=multi-user.target
```

またサーバーの起動コマンドは `ExecStart=` 部分で実行されています。
またその中の `PalServer.sh` にはオプションが存在します。（[公式ガイド](https://tech.palworldgame.com/dedicated-server-guide#settings)）
環境に合わせて任意で編集してください。
| PalServer.sh オプション | 意味 |
|---|---|
| `port=8211` | サーバーの待ち受けに使うポート番号を変更します。 |
| `players=32` | サーバーの最大参加人数を変更します。 |
| `EpicApp=PalServer` | コミュニティサーバーとして設定します。下には含まれていません。[※参考](https://tech.palworldgame.com/community-server-guide) |
| `-useperfthreads -NoAsyncLoadingThread -UseMultithreadForDS` | マルチスレッドCPU環境において性能を向上させます。最大で4スレッド程度まで効果がありこれ以上のスレッド数を割り当ててもあまり意味がありません。 |

### 7. ゲーム起動
daemon をリロードしてサーバを起動させましょう。自動起動もするようにします。
```Bash
# デーモンの再起動
sudo systemctl daemon-reload

# 自動起動設定
sudo systemctl enable palworld-dedicated.service

# サーバの起動
sudo systemctl start palworld-dedicated.service
```

### 必要に応じたコマンド
```Bash
# ゲームサーバを停止させたいとき
sudo systemctl stop palworld-dedicated.service

# ゲームサーバの起動状態を確認するとき
systemctl status palworld-dedicated.service

# ゲームサーバが出力したログを確認するとき
sudo journalctl -u palworld-dedicated.service
```

### 8. ポート開放
ポートを開放設定をします。ゲームは `UDP 8211` を利用します。
お使いのサーバが置かれた環境によって手順は異なるので、環境のキーワードと合わせて検索してください。
AlmaLinux では firewalld の設定が必要です。

例として参考ページを挙げます。
* [Linux firewalldでポート開放する方法（RHEL/CentOS7系） #Linux - Qiita](https://qiita.com/kawasukeeee/items/bcffcc018919f89942c1)
* [Ubuntuのポート開放方法（ファイアウォールを設定する） #Ubuntu - Qiita](https://qiita.com/siida36/items/be21d361cf80d664859c)
* [ポートを開放する方法 | バッファロー](https://www.buffalo.jp/support/faq/detail/15022.html)

### 9. サーバーへ接続

ゲームを起動し、「マルチプレイに参加する（専用サーバー）」を押下します。

:::note warn
Xbox PC Game Pass版はこのメニューが表示されず、専用サーバーに参加できません。
:::
![Monosnap Pal 2024-01-19 22.47.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/7196cd5e-5880-6f0e-155c-bf1dafc0d579.png)

`サーバのIPアドレス:8211` に接続します。
![Monosnap Pal 2024-01-19 22.49.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/5f556a77-f114-9908-737b-94888fed8f1f.png)

まだ読み終わるのは早いです。セーブデータが破損する不具合も発見されています。
継続した運用をするのであれば、最低限 [セーブデータバックアップ](#セーブデータバックアップ) の設定を行いましょう。

## サーバ設定変更
```Bash
# デフォルト設定ファイルをコピー
cd /home/palworld/Steam/palworld
cp DefaultPalWorldSettings.ini Pal/Saved/Config/LinuxServer/PalWorldSettings.ini

# サーバを停止
sudo systemctl stop palworld-dedicated.service

# 任意の値を変更
vi Pal/Saved/Config/LinuxServer/PalWorldSettings.ini

# サーバの起動（設定反映）
sudo systemctl start palworld-dedicated.service
```
設定を誤るとセーブデーターが破損する場合があります。
稼働中のサーバー設定を変更する場合、先に [バックアップ](#セーブデータバックアップ) の設定をしてから、サーバー設定の編集を推奨します。

またサーバーの稼働状態では `PalWorldSettings.ini` に値が上書きされる動作が起こるようです。
必ず停止してから編集を行って下さい。

設定パラメータ一覧は公式の [Palworld tech guide - Optimize game balance](https://tech.palworldgame.com/optimize-game-balance) を参照してください。

`PalWorldSettings.ini` は横に長く、大変見辛い一行のファイルですが、改行を挟むと初期値に戻ってしまいます。
ServerDescription の設定では `\n` を使うことで改行が可能です。

こちらの [Palworldサーバ設定ジェネレーター](https://palworld-setting-generator.deno.dev/) を利用すれば好みの設定値を簡単に作成できます。

### 設定に関する不具合情報
![Monosnap Palworld tech guide - Dedicated server guide - Google Chrome 2024-01-20 08.29.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/af5203e6-694c-dffd-3c9b-c4e41f0f84d2.png)
([Palworld tech guide - Dedicated server guide](https://tech.palworldgame.com/dedicated-server-guide) より)

~~現状 `ServerPassword` を設定すると、IPアドレス指定でサーバにログインできません。~~
~~コミュニティサーバ一覧の検索機能は取得したページ内での検索であり、大量のコミュニティサーバが存在する現在では、目的のプライベートなサーバーを探すのに一つ一つページを辿る必要があり、現実的ではありません。~~

~~よって不特定多数からのログイン求めない場合は、 `ServerPassword` は空（パスワードなし）にして IP アドレスを隠して運用するのを推奨します。~~

（01/26 追記）
パスワードを指定してログインができるよう、ゲームがアップデートされました。
パスワードが設定されたサーバーにログインするときは、✔をいれるとサーバーパスワードの入力画面が表示されます。
![Monosnap Pal 2024-01-27 19.09.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/1f34cdc9-e691-fcd8-fe82-e9acd78e8cbc.png)

## 手動サーバアップデート
インストール時に利用した steamcmd コマンドを使ってアップデートしましょう。
```Bash
cd /home/palworld/

# steamcmd.sh を実行
./steamcmd.sh

# ユーザ認証は不要なので anonymous でログインします
> login anonymous

# /home/palworld/Steam/palworld/ をインストールディレクトリに指定します
> force_install_dir /home/palworld/Steam/palworld/

# Palworld の App ID である 2394010 を指定してアップデート
> app_update 2394010 validate

# progress が 100 になるのを待つ

# steamcmd を抜ける
> quit
```
`+`で繋ぐことで一行で実行することもできます。
```Bash
./steamcmd.sh +force_install_dir /home/palworld/Steam/palworld +login anonymous +app_update 2394010 validate +quit
```
また[スクリプトを組めば](#自動アップデート)自動でアップデートすることも可能です。

## セーブデータバックアップ
:::note
（01/27 更新）
`PalWorldSettings.ini` もバックアップされるよう、バックアップするディレクトリを `./palworld/Pal/Saved` 配下に変更しました
:::

パルワールドのセーブデータ、設定いじってたりしてミスるとすぐ飛びます。
私も一度飛ばしました。バックアップ絶対やりましょう。

:::note alert
**現時点で確認されている不具合**
ギルドメンバーがギルドを脱退すると、その時オフライン中のメンバーの個人データが壊れ、壊れたユーザーがログインしようとすると無限ロード画面になる
:::

この不具合は参加者が意図的に引き起こすこともできますので、サーバー管理者はセーブデーターを定期的にバックアップしロールバックできる状態にしておくことが望ましいです。
バックアップスクリプトを用意しましょう。
```Bash
# バックアップ先のディレクトリ
mkdir -p /home/palworld/backup/

# バックアップ用スクリプト
vi /home/palworld/backup-palworld.sh
```
```Bash
#!/bin/bash

# Define source directory and backup directory
SOURCE_DIR="/home/palworld/Steam/palworld/Pal/Saved"
BACKUP_DIR="/home/palworld/backup"
DATE=$(date +%Y%m%d_%H%M%S)

# Create a gzip compressed tarball
tar -czf "${BACKUP_DIR}/backup_${DATE}.tar.gz" -C "${SOURCE_DIR}" .

echo "Backup of ${SOURCE_DIR} completed at ${BACKUP_DIR}/backup_${DATE}.tar.gz"
```
実行権限をつけて、試しにバックアップ
```Bash
# 実行権限をつける
chmod +x /home/palworld/backup-palworld.sh
# 試しに実行
/home/palworld/backup-palworld.sh
```
バックアップが成功したメッセージが表示されれば、きちんと動作しています。
```
[palworld@localhost ~]$ /home/palworld/backup-palworld.sh
Backup of /home/palworld/Steam/palworld/Pal/Saved completed at /home/palworld/backup/backup_20240202_151912.tar.gz
```
これを cron で1時間毎に実行するようにします。
```Bash
# Asia/Tokyo にセット
sudo timedatectl set-timezone Asia/Tokyo

crontab -e
```
```
0 * * * * /home/palworld/backup-palworld.sh
```

（参考元: [Palworld Server Community FAQ #Backup Scripts](https://gist.github.com/Toakan/3c78a577c21a21fcc5fa917f3021d70e#backup-scripts)）

<details><summary>バックアップからロールバック（データ復元）を行う場合</summary><div>

```Bash
# サーバー停止
sudo systemctl stop palworld-dedicated.service

# 壊れた状態にも戻せるよう、一応バックアップを作成
/home/palworld/backup-palworld.sh

# ./Saved/SaveGames/0 の中を削除
rm -rf /home/palworld/Steam/palworld/Pal/Saved*

# ロールバックを行う
tar -xzf backup_(復元先の時刻).tar.gz -C "/home/palworld/Steam/palworld/Pal/Saved"

# サーバー起動
sudo systemctl start palworld-dedicated.service
```
</div></details>

## 自動アップデート
:::note
（01/27 更新）
最新の Build ID 取得に api.steamcmd.net を利用し、Steam サーバに大きな負荷を掛けないようにしました。この動作であれば短時間でスクリプトを回しても影響が少ないので、実行間隔を30分にしています。
:::

配布されているサーバプログラムを定期的にチェックし、インストールされたものと異なる場合はアップデートがあったと見なして、自動でアップデート&サーバを再起動するようにします。
`update-palworld.sh` を作成します。
```Bash
# json を処理するための jq コマンドをインストール
sudo dnf install -y jq
# アップデートスクリプトを作成
vi /home/palworld/update-palworld.sh
```

```Bash
#!/bin/sh

# Paths
Steamcmd="/home/palworld/steamcmd.sh"
install_dir="/home/palworld/Steam/palworld"
service_name="palworld-dedicated.service"

echo "# Environment Check"
date

# Retrieve the current Build ID
OLD_Build=`$Steamcmd +force_install_dir $install_dir +login anonymous +app_status 2394010 +quit | grep -e "BuildID" | awk '{print $8}'`
echo "Current BuildID: $OLD_Build"

# Attempt to fetch the New Build ID using curl
NEW_Build=$(curl -s https://api.steamcmd.net/v1/info/2394010 | jq -r '.data["2394010"].depots.branches.public.buildid')

# Fallback to SteamCMD method if curl fails to retrieve data
if [ -z "$NEW_Build" ] || [ "$NEW_Build" = "null" ]; then
    echo "Failed to fetch New BuildID with curl. Resorting to SteamCMD."
    $Steamcmd +force_install_dir $install_dir +login anonymous +app_update 2394010 validate +quit > /dev/null
    NEW_Build=`$Steamcmd +force_install_dir $install_dir +login anonymous +app_status 2394010 +quit | grep -e "BuildID" | awk '{print $8}'`
fi

echo "Fetched New BuildID: $NEW_Build"

# Update the server if the Build IDs do not match
if [ "$OLD_Build" = "$NEW_Build" ]; then
    echo "No update required. Build numbers are identical."
else
    echo "# Updating the game server..."
    $Steamcmd +force_install_dir $install_dir +login anonymous +app_update 2394010 validate +quit > /dev/null
    echo "Game server updated successfully to BuildID: $NEW_Build"
    
    echo "Restart ${service_name} because an game update exists."
    sudo systemctl stop $service_name
    sudo systemctl start $service_name
    systemctl status $service_name
fi
```

実行権限をつけます
```
chmod +x /home/palworld/update-palworld.sh
```
試しに実行してみましょう。
```
/home/palworld/update-palworld.sh
```
問題なければ cron で定期的に実行するようにします。
実行ユーザーが `sudo systemctl start palworld-dedicated.service` を実行したときパスワード認証が要求されない環境であることを確認してください。
```
sudo crontab -e
```
```
*/30 * * * * /home/palworld/update-palworld.sh
```

30分毎にアップデートがあるかチェックが入り、あればアップデートを行ってサーバーが再起動します。

またこれだと接続ユーザーは予告なくサーバーがシャットダウンしていまうので、[RCON を組み合わせて](#rcon-で使う)、サーバー再起動前にアナウンスを行うようにもできます。

<details><summary>RCON を組み合わせて、サーバー再起動前にアナウンスを行う場合</summary><div>

rcon-cli は `/home/palworld/rcon/rcon` にある前提です。

```Bash
vi /home/palworld/update-palworld.sh
```
```Bash
#!/bin/sh

# Paths
Steamcmd="/home/palworld/steamcmd.sh"
rcon_cli="/home/palworld/rcon/rcon"
install_dir="/home/palworld/Steam/palworld"
service_name="palworld-dedicated.service"

echo "# Environment Check"
date

# Retrieve the current Build ID
OLD_Build=`$Steamcmd +force_install_dir $install_dir +login anonymous +app_status 2394010 +quit | grep -e "BuildID" | awk '{print $8}'`
echo "Current BuildID: $OLD_Build"

# Attempt to fetch the New Build ID using curl
NEW_Build=$(curl -s https://api.steamcmd.net/v1/info/2394010 | jq -r '.data["2394010"].depots.branches.public.buildid')

# Fallback to SteamCMD method if curl fails to retrieve data
if [ -z "$NEW_Build" ] || [ "$NEW_Build" = "null" ]; then
    echo "Failed to fetch New BuildID with curl. Resorting to SteamCMD."
    $Steamcmd +force_install_dir $install_dir +login anonymous +app_update 2394010 validate +quit > /dev/null
    NEW_Build=`$Steamcmd +force_install_dir $install_dir +login anonymous +app_status 2394010 +quit | grep -e "BuildID" | awk '{print $8}'`
fi

echo "Fetched New BuildID: $NEW_Build"

# Update the server if the Build IDs do not match
if [ "$OLD_Build" = "$NEW_Build" ]; then
    echo "No update required. Build numbers are identical."
else
    echo "# Updating the game server..."
    $Steamcmd +force_install_dir $install_dir +login anonymous +app_update 2394010 validate +quit > /dev/null
    echo "Game server updated successfully to BuildID: $NEW_Build"

    echo "Initiating ${service_name} restart due to an update."
    ${rcon_cli} -a "127.0.0.1:25575" -p {設定したAdminパスワード} "Broadcast The-server-will-restart-in-60-seconds.Please-prepare-to-exit-the-game."
    sleep 30
    ${rcon_cli} -a "127.0.0.1:25575" -p {設定したAdminパスワード} "Broadcast The-server-will-restart-in-30-seconds.Please-prepare-to-exit-the-game."
    sleep 20
    ${rcon_cli} -a "127.0.0.1:25575" -p {設定したAdminパスワード} "Broadcast The-server-will-restart-in-10-seconds.Please-prepare-to-exit-the-game."
    sleep 10
    sudo systemctl stop $service_name
    sudo systemctl start $service_name
    systemctl status $service_name
fi
```
</div></details>

## セーブデータの互換性
パルワールド Dedicated Server は以下の階層にセーブデータを保存します。この階層配下にワールドIDで保存されます。
```
./Pal/Saved/SaveGames/0/
```

またマルチプレイを行う方法として 招待コードマルチプレイ、Windows Dedicated Server、Linux Dedicated Server、SteamCMD サーバーがありますが、それぞれのセーブデータ間には互換性がありません。

セーブデーターのコンバートを行う必要があります。下記ガイドをご覧ください。
* [Coop セーブ ファイルを専用サーバーに転送するための完全ガイド (英語)](https://www.reddit.com/r/Palworld/comments/19cb8su/complete_guide_to_transfer_a_coop_save_file_incl/)
* [移行のためのスクリプトとガイド (英語)](https://github.com/xNul/palworld-host-save-fix)


## Admin コマンド
サーバーを管理する上で必要になる、ユーザーのキックやメッセージのブロードキャストなどを管理者操作を必要とする場合、設定の変更が必要になります。

| コマンド | 詳細 |
|---|---|
| /Shutdown {Seconds} {MessageText} | Seconds で指定した秒数が経過後にサーバーをシャットダウンします。MessageText に記入した内容がサーバー参加者に通知されます。 |
| /DoExit | サーバーを強制終了します。 |
| /Broadcast {MessageText} | サーバー参加者すべてに対してメッセージを送信します。 |
| /KickPlayer {SteamID} | 指定したSteamIDを持つプレイヤーをサーバーからキックします。 |
| /BanPlayer {SteamID} | 指定したSteamIDを持つプレイヤーをサーバーからBANします。 |
| /TeleportToPlayer {SteamID} | ゲーム内でのみ実行可能。指定したSteamIDを持つプレイヤーの現在地に対してテレポートします。 |
| /TeleportToMe {SteamID} | ゲーム内でのみ実行可能。指定したSteamIDを持つプレイヤーをコマンド実行者の座標へテレポートします。 |
| /ShowPlayers | 現在サーバーに参加しているすべてのプレイヤー情報を表示します。RCON経由ではマルチバイトを含むユーザ名のユーザが居るとうまく取得できません。 |
| /Info | サーバーの情報を表示します。 |
| /Save | ワールドデータをディスクに保存します。 |

([Palworld tech guide - Server commands](https://tech.palworldgame.com/server-commands) より引用)

### 有効化設定
Admin コマンドを利用するには、`PalWorldSettings.ini` で `AdminPassword=""`の設定が必要です。
またゲームにログインせずにサーバーのコンソールや同じネットワークから操作が可能になる RCON という仕組みを利用する場合、RCONEnabled も設定しましょう。
([Source RCON Protocol - Valve Developer Community](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol))

`{設定したいAdminパスワード}` を任意の文字列にして実行してください。
```Bash
# 編集前にサーバーを停止
sudo systemctl stop palworld-dedicated.service

# Palworld インストールディレクトリに移動
cd /home/palworld/Steam/palworld

# PalWorldSettings.ini の AdminPassword を変更
sed -i 's/AdminPassword="[^"]*"/AdminPassword="{設定したいAdminパスワード}"/' Pal/Saved/Config/LinuxServer/PalWorldSettings.ini

# RCON を利用する場合のみ
# PalWorldSettings.ini の RCONEnabled=False を True に変更
# RCONPort はデフォルトで 25575
sed -i 's/RCONEnabled=False/RCONEnabled=True/' Pal/Saved/Config/LinuxServer/PalWorldSettings.ini

# PalWorldSettings.ini の値を確認
cat Pal/Saved/Config/LinuxServer/PalWorldSettings.ini

# サーバー起動
sudo systemctl start palworld-dedicated.service
```

### ゲーム内で使う
1. チャットを開き「/AdminPassword {Adminパスワード}」をチャットし、管理者になる
2. `/` から始まる Admin コマンドを入力する（日本語も可能）

### RCON で使う
#### RCON クライアントの入手
[gorcon/rcon-cli](https://github.com/gorcon/rcon-cli) のビルド済みリリースを入手します。
```Bash
# rcon 用のディレクトリを準備
mkdir -p /home/palworld/rcon/
cd /home/palworld/rcon/

# ビルド済みリリース amd64_linux を解凍
curl -LO https://github.com/gorcon/rcon-cli/releases/download/v0.10.3/rcon-0.10.3-amd64_linux.tar.gz
# rcon-0.10.3-amd64_linux/rcon を今の階層に
tar -xvzf rcon-0.10.3-amd64_linux.tar.gz --strip-components=1
rm -f rcon-0.10.3-amd64_linux.tar.gz

# ゲームのサーバー内チャットに Broadcast してみる
./rcon -a "127.0.0.1:25575" -p {設定したAdminパスワード} "Broadcast Hello!"
```

#### rcon コマンドの実行
現時点のパスワールドでは RCON 経由の Admin コマンドの実行に注意点があります。
* 最初の `/` は不要
* 日本語が利用できない
* 半角スペースが利用できない

よって RCON でメッセージを送る場合は、_ や - を混ぜた英語としましょう。

例えば「アップデートがあるので1分後にシャットダウンすること」をユーザーに伝えつつシャットダウンする場合は
```
./rcon -a "127.0.0.1:25575" -p {設定したAdminパスワード} "Shutdown 60 The-server-will-shutdown-in-60-seconds.Please-prepare-to-exit-the-game."
```
となります。

## 自動再起動によるメモリ管理
:::note
（01/29 更新）追加しました。
:::
[サーバを起動し続けるとサーバが落ちる](#サーバを起動し続けるとサーバが落ちる) 問題に対処するため、サーバー上のメモリ使用率を定期的にチェックし、閾値を超えていた場合はパルワールドサーバーの再起動を行って低いメモリ使用率を維持するようにします。

`check-palworld-memory.sh` を作成します。
```Bash
# メモリ監視スクリプトを作成
vi /home/palworld/check-palworld-memory.sh
```
メモリ使用率が 80% を超えた場合、サーバーの再起動を行います。
また[スワップ領域を作成していて](#解決策-1)、それも計算に含む場合は `CHECK_SWAP=True` にします。
```Bash
#!/bin/bash

# Palworld Service Name
SERVICE_NAME="palworld-dedicated.service"

# サーバー再起動を行うメモリ使用率(%)の閾値 
THRESHOLD=80
# スワップ領域を含む閾値とするか
CHECK_SWAP=False

mem_usage=$(free -b | awk '/Mem:/ {print int($3/$2 * 100.0)}')
mem_total_gb=$(free -b | awk '/Mem:/ {printf "%.1f", $2/1024^3}')
mem_used_gb=$(free -b | awk '/Mem:/ {printf "%.1f", $3/1024^3}')
swap_total=$(free -b | awk '/Swap:/ {print $2}')
swap_usage=$(free -b | awk '/Swap:/ {if($2 > 0) print int($3/$2 * 100.0); else print 0;}')
swap_total_gb=$(free -b | awk '/Swap:/ {printf "%.1f", $2/1024^3}')
swap_used_gb=$(free -b | awk '/Swap:/ {printf "%.1f", $3/1024^3}')

current_time=$(date +"%Y-%m-%d %H:%M:%S")

if [ "$CHECK_SWAP" = True ] && [ $swap_total -gt 0 ]; then
    if [ $swap_usage -gt $THRESHOLD ]; then
        # スワップ使用率が閾値を超えていた場合
        sudo systemctl stop $SERVICE_NAME
        sudo systemctl start $SERVICE_NAME
        echo "The $SERVICE_NAME has been restarted."
        echo "[$current_time] Restarted $SERVICE_NAME due to high swap usage: $swap_usage% (${swap_used_gb}GB/${swap_total_gb}GB)"
        exit 0
    fi
elif [ $mem_usage -gt $THRESHOLD ]; then
    # メモリ使用率が閾値を超えていた場合
    sudo systemctl stop $SERVICE_NAME
    sudo systemctl start $SERVICE_NAME
    echo "The $SERVICE_NAME has been restarted."
    echo "[$current_time] Restarted $SERVICE_NAME due to high memory usage: $mem_usage% (${mem_used_gb}GB/${mem_total_gb}GB)"
    exit 0
fi

echo "[$current_time] Current memory usage: $mem_usage% (${mem_used_gb}GB/${mem_total_gb}GB)"
if [ $swap_total -gt 0 ]; then
    echo "[$current_time] Current swap usage: $swap_usage% (${swap_used_gb}GB/${swap_total_gb}GB)"
fi
```

実行権限をつけます
```
chmod +x /home/palworld/check-palworld-memory.sh
```
試しに実行してみましょう。
```
/home/palworld/check-palworld-memory.sh
```
閾値を上回る場合、パルワールドのサービスがリスタートします。
```Bash
# 閾値を下回る場合
[palworld@localhost ~]$ /home/palworld/check-palworld-memory.sh
[2024-01-29 03:42:33] Current memory usage: 24% (4.2GB/17.3GB)
[2024-01-29 03:42:33] Current swap usage: 0% (0.0GB/7.9GB)
# 閾値を上回る場合
[palworld@localhost ~]$ /home/palworld/check-palworld-memory.sh
The palworld-dedicated.service has been restarted.
[2024-01-29 03:44:02] Restarted palworld-dedicated.service due to high memory usage: 91% (15.8GB/17.3GB)
```
出力に問題なければ cron で定期的に実行するようにします。
```
sudo crontab -e
```
```
*/30 * * * * /home/palworld/check-palworld-memory.sh
```

30分毎にメモリ使用率をチェックし、閾値を超えていればサーバーが再起動します。

またこれだと接続ユーザーは予告なくサーバーがシャットダウンしていまうので、[RCON を組み合わせて](#rcon-で使う)、サーバー再起動前にアナウンスを行うようにもできます。

<details><summary>RCON を組み合わせて、サーバー再起動前にアナウンスを行う場合</summary><div>

rcon-cli は `/home/palworld/rcon/rcon` にある前提です。
`{設定したAdminパスワード}` を置換してください。

```Bash
vi /home/palworld/check-palworld-memory.sh
```
```Bash
#!/bin/bash

# Paths
SERVICE_NAME="palworld-dedicated.service"
rcon_cli="/home/palworld/rcon/rcon"
Palworld_admin_Password="{設定したAdminパスワード}"

# サーバー再起動を行うメモリ使用率(%)の閾値 
THRESHOLD=80
# スワップ領域を含む閾値とするか
CHECK_SWAP=False

mem_usage=$(free -b | awk '/Mem:/ {print int($3/$2 * 100.0)}')
mem_total_gb=$(free -b | awk '/Mem:/ {printf "%.1f", $2/1024^3}')
mem_used_gb=$(free -b | awk '/Mem:/ {printf "%.1f", $3/1024^3}')
swap_total=$(free -b | awk '/Swap:/ {print $2}')
swap_usage=$(free -b | awk '/Swap:/ {if($2 > 0) print int($3/$2 * 100.0); else print 0;}')
swap_total_gb=$(free -b | awk '/Swap:/ {printf "%.1f", $2/1024^3}')
swap_used_gb=$(free -b | awk '/Swap:/ {printf "%.1f", $3/1024^3}')

current_time=$(date +"%Y-%m-%d %H:%M:%S")

# RCON を利用したサーバー再起動処理
restart_server() {
    echo "Due to exceeding the threshold, the game server will be restarted in 5 minutes."
    $rcon_cli -a "127.0.0.1:25575" -p $Palworld_admin_Password "Broadcast [high-memory]The-server-will-be-restarted-in-5-minutes.Please-prepare-to-exit-the-game."
    sleep 240 # 4 minutes
    $rcon_cli -a "127.0.0.1:25575" -p $Palworld_admin_Password "Broadcast [high-memory]The-server-will-be-restarted-in-60-seconds.Please-prepare-to-exit-the-game."
    sleep 30 
    $rcon_cli -a "127.0.0.1:25575" -p $Palworld_admin_Password "Broadcast [high-memory]The-server-will-be-restarted-in-30-seconds.Please-prepare-to-exit-the-game."
    sleep 20
    $rcon_cli -a "127.0.0.1:25575" -p $Palworld_admin_Password "Broadcast [high-memory]The-server-will-be-restarted-in-10-seconds.Please-prepare-to-exit-the-game."
    $rcon_cli -a "127.0.0.1:25575" -p $Palworld_admin_Password "Save"
    sleep 10   # 10 seconds
    sudo systemctl stop $SERVICE_NAME
    sudo systemctl start $SERVICE_NAME
    echo "The $SERVICE_NAME has been restarted."
}

if [ "$CHECK_SWAP" = True ] && [ $swap_total -gt 0 ]; then
    if [ $swap_usage -gt $THRESHOLD ]; then
        # スワップ使用率が閾値を超えていた場合
        restart_server
        echo "[$current_time] Stopped $SERVICE_NAME due to high swap usage: $swap_usage% (${swap_used_gb}GB/${swap_total_gb}GB)"
        exit 0
    fi
elif [ $mem_usage -gt $THRESHOLD ]; then
    # メモリ使用率が閾値を超えていた場合
    restart_server
    echo "[$current_time] Stopped $SERVICE_NAME due to high memory usage: $mem_usage% (${mem_used_gb}GB/${mem_total_gb}GB)"
    exit 0
fi

echo "[$current_time] Current memory usage: $mem_usage% (${mem_used_gb}GB/${mem_total_gb}GB)"
if [ $swap_total -gt 0 ]; then
    echo "[$current_time] Current swap usage: $swap_usage% (${swap_used_gb}GB/${swap_total_gb}GB)"
fi
```

ゲーム内ではサーバーの再起動がこのように予告されます。

![palworld-memory-restart-notify.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/a0d34ecb-1a22-c005-a34e-5cf3f646d6c7.png)

</div></details>

## 不具合情報
サーバー側で解消可能な不具合情報をまとめています。

### [S_API] SteamAPI_Init(): Sys_LoadModule failed to load: … .steam/sdk64/steamclient.so
サーバーインストール時に下記のようなエラーが表示される
```
[palworld@localhost palworld]$ ./PalServer.sh 
Shutdown handler: initalize.
Increasing per-process limit of core file size to infinity.
sh: xdg-user-dir: コマンドが見つかりません
dlopen failed trying to load:
steamclient.so
with error:
steamclient.so: cannot open shared object file: No such file or directory
dlopen failed trying to load:
/home/palworld/.steam/sdk64/steamclient.so
with error:
/home/palworld/.steam/sdk64/steamclient.so: cannot open shared object file: No such file or directory
[S_API] SteamAPI_Init(): Sys_LoadModule failed to load: /home/palworld/.steam/sdk64/steamclient.so
dlopen failed trying to load:
steamclient.so
with error:
```
#### 解決策
Steamworks SDK Redist をダウンロードしてコピーする
```Bash
mkdir -p /home/palworld/.steam/sdk64/
/home/palworld/steamcmd.sh +login anonymous +app_update 1007 +quit
cp /home/palworld/Steam/steamapps/common/Steamworks\ SDK\ Redist/linux64/steamclient.so /home/palworld/.steam/sdk64/steamclient.so
```
<details><summary>これで動くようにはなる</summary><div>

```Text
[palworld@localhost palworld]$ ./PalServer.sh 
Shutdown handler: initalize.
Increasing per-process limit of core file size to infinity.
sh: xdg-user-dir: コマンドが見つかりません
dlopen failed trying to load:
steamclient.so
with error:
steamclient.so: cannot open shared object file: No such file or directory
[S_API] SteamAPI_Init(): Loaded '/home/palworld/.steam/sdk64/steamclient.so' OK.  (First tried local 'steamclient.so')
CAppInfoCacheReadFromDiskThread took 3 milliseconds to initialize
dlmopen steamservice.so failed: steamservice.so: cannot open shared object file: No such file or directory
Setting breakpad minidump AppID = 2394010
[S_API FAIL] Tried to access Steam interface SteamUser021 before SteamAPI_Init succeeded.
[S_API FAIL] Tried to access Steam interface SteamFriends017 before SteamAPI_Init succeeded.
[S_API FAIL] Tried to access Steam interface STEAMAPPS_INTERFACE_VERSION008 before SteamAPI_Init succeeded.
[S_API FAIL] Tried to access Steam interface SteamNetworkingUtils004 before SteamAPI_Init succeeded.
```
</div></details>

### サーバを起動し続けるとサーバが落ちる
Dedicated Server にはメモリリークが発生する不具合があるようです。
* [Dedicated Servers: Bug found! :: Palworld / パルワールド 総合掲示板](https://steamcommunity.com/app/1623730/discussions/0/4132683013930082136/)

またサーバプログラムが原因のクラッシュは `./Pal/Saved/Crashes/` ディレクトリ内にクラッシュ毎にログが記録されます。

#### 解決策
Dedicated Server は CPU よりもメモリの消費が特に大きいようで、~~8GB 以上の割り当て~~（公式が 16GB に訂正されました）で安定稼働するように見られます。
また継続したゲームプレイをすることで、最初は少ないメモリ消費でも20～30GBなど無尽蔵に肥大化していく傾向にあります。サーバーを再起動することでリセットできます。

メモリが限られる環境であれば、スワップ領域を割り当てることで 4GB などの少ないメモリでも比較的安定したプレイができます。

```Bash
# 16GB のスワップ領域を作成する
sudo fallocate -l 16G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon -p 10 /swapfile

# /etc/fstab に /swapfile none swap sw,pri=10 0 0 を記載する
echo '/swapfile none swap sw,pri=10 0 0' | sudo tee -a /etc/fstab

# 作成されたスワップ領域を確認する
swapon -s 
```

また自宅サーバーやクラウドなど、大きいメモリを載せたサーバーを最初から用意できるのであれば、スワップ領域は使わずに 16GB や 32GB などの割り当てを推奨します。
またスワップでも解決しないようであれば、素直にサーバースペックを上げましょう。

### ログインするとキャラクリから始まってしまう
こちらのページやツイートが参考になります。
* [Palworld アップデート後キャラクリから始まってしまう(初期化バグ) #Linux - Qiita](https://qiita.com/sakanaJA/items/b547277a08c1acb5c35b)


<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">パルワールド、アップデートで鯖再起したらまたキャラクリに飛ばされる不具合起きたので試してみたけど2人中2人復旧できたから直し方これで確定っぽい<br><br>Playersフォルダの中身落としてjsonに変換して<br>&quot;save_game_class_name&quot;: &quot;None.PalWorldPlayerSaveGame&quot;<br>になってるところを→</p>&mdash; RickyZX🧢 (@rkzx0) <a href="https://twitter.com/rkzx0/status/1752926946097529204?ref_src=twsrc%5Etfw">February 1, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### 累計パル数が7000を超えると無限ロードになる
サーバー内のいずれかのギルドに参加しているユーザーが捕獲・孵化させた累計パル数が7000を超えると、ゲームが無限ロードになる

:::note
（02/07 更新）
Steam版 patch v0.1.4.1 で完全に修正されたようです。
:::

<details><summary>修正前の記載内容</summary><div>

#### 解決策
`Level.sav` の `individual_character_handle_ids` の配列をすべて消すと解決するとのこと
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/%E3%83%91%E3%83%AB%E3%83%AF%E3%83%BC%E3%83%AB%E3%83%89?src=hash&amp;ref_src=twsrc%5Etfw">#パルワールド</a><br>同志ヒワミの活躍により捕獲一定数超え無限LDを解決しました。 SaveGames/0/データ名/Level.sav のファイルをJSONに変えてVSCODEにぶっこんで以下画像の&quot;group_type&quot;: &quot;EPalGroupType::Guild&quot;の下のほうにある&quot;individual_character_handle_ids&quot;の配列すべて消せばいけました。 <a href="https://t.co/OP7C0aGpzA">pic.twitter.com/OP7C0aGpzA</a></p>&mdash; 一般兵 (@Abyss_watchers3) <a href="https://twitter.com/Abyss_watchers3/status/1751262008446722224?ref_src=twsrc%5Etfw">January 27, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

またこちらのページも参考になるかもしれません。
* [【パルワールド】パル7000体捕獲すると起きるクラッシュ問題を解決してみる #Palworld - Qiita](https://qiita.com/puipon/items/aa178fb3f49c98abbeab)

</div></details>

### サーバーがラグい
#### 解決策
Unreal Engine の設定ファイルである `Engine.ini` ファイルに設定を編集することで、パフォーマンス改善に繋がる場合があるようです。
* [Optimizing PalWorld Dedicated Server: Engine.ini Settings to Reduce Lag and Rubberbanding : r/Palworld](https://www.reddit.com/r/Palworld/comments/19f6i19/)

## 参考リンク
サーバー運用する上で参考になるその他のページ

| リンク  | 説明 | 
|---|---| 
| [palworldのdedicatedサーバーをユーザごとの固有サービスとして起動する話 #Ubuntu22.04 - Qiita](https://qiita.com/poseyamada/items/269fb52e9d3cc11c1078) | 当記事をベースに、systemd サービスをユーザの固有サービスとして起動する方法を記載くださっています。 |
| [Palworld Server Community FAQ](https://gist.github.com/Toakan/3c78a577c21a21fcc5fa917f3021d70e) | 英語圏の Server Community FAQ です。よくまとまっています。 | 
| [REST API \| Palworld tech guide](https://tech.palworldgame.com/ja/category/rest-api) (公式ガイド) | バージョン 0.2.1.0 から REST API でもサーバーの情報を取得できるようになったようです。RCON よりも柔軟で使いやすくなっています。 |