palworldのdedicatedサーバーをユーザごとの固有サービスとして起動する話

α版としてリリースされたpalworldのdedicatedサーバーは定期的な再起動が必要です。  
メモリの消費量が激しく、環境によっては日に複数回のプロセスの再起動が必要になります。  

systemdの`timer`や`RuntimeMaxSec`を使う方法もありますが、今回は`cron`を使って制御をしてみました。
cronで扱うためにサービスをユーザーレベルに変更した、というのが今回の経緯です。
おそらくもっとシンプルなやり方もあるのでしょうが、自分の忘備録として残しておきます。

# 環境
| 名前 | バージョン |
| :--- | :--------|
| OS   |  Ubuntu 22.04 |

基本的な構築は下記の記事を踏襲しています。
起動シェルとサービスの登録（とcron)が変更箇所になります。

https://qiita.com/naoya-i/items/e907a6b949e5da36d532



# 起動用のシェルの作成
<details><summary> 知識</summary>

`tmux`は`screen`と同じような仮想ターミナルを作成することができます。
下記の記事が参考になります。


https://qiita.com/shin-ch13/items/9d207a70ccc8467f7bab


今回はサーバーの起動を`tmux`経由で起動します。
起動時のログが消えず、万が一エラーが発生していた際に後から追うことができます。

</details>

### 起動シェル
`/home/palworld/Steam/steamapps/common/PalServer/boot.sh  `

<details><summary>起動シェル例</summary>


```shell
#!/bin/bash

TMUX_NAME=palworld-server

case $1 in
        start)
                tmux new-session -s $TMUX_NAME -d "/home/palworld/Steam/steamapps/common/PalServer/PalServer.sh -useperfthreads -NoAsyncLoadingThread -UseMultithreadForDS"
                sleep 10;;
        stop)
                /home/palworld/rcon/rcon -a "127.0.0.1:25575" -p password "Broadcast The-server-will-restart-in-60-seconds.Please-prepare-to-exit-the-game."
                sleep 30
                /home/palworld/rcon/rcon -a "127.0.0.1:25575" -p password "Broadcast The-server-will-restart-in-30-seconds.Please-prepare-to-exit-the-game."
                sleep 20
                /home/palworld/rcon/rcon -a "127.0.0.1:25575" -p password "Broadcast The-server-will-restart-in-10-seconds.Please-prepare-to-exit-the-game."
                sleep 5
                /home/palworld/rcon/rcon -a "127.0.0.1:25575" -p password "Broadcast The-server-will-restart-in-5-seconds.Please-prepare-to-exit-the-game."
                sleep 1
                /home/palworld/rcon/rcon -a "127.0.0.1:25575" -p password "Broadcast The-server-will-restart-in-4-seconds.Please-prepare-to-exit-the-game."
                sleep 1
                /home/palworld/rcon/rcon -a "127.0.0.1:25575" -p password "Broadcast The-server-will-restart-in-3-seconds.Please-prepare-to-exit-the-game."
                sleep 1
                /home/palworld/rcon/rcon -a "127.0.0.1:25575" -p password "Broadcast The-server-will-restart-in-2-seconds.Please-prepare-to-exit-the-game."
                sleep 1
                /home/palworld/rcon/rcon -a "127.0.0.1:25575" -p password "Broadcast The-server-will-restart-in-1-seconds.Please-prepare-to-exit-the-game."
                sleep 5
                /home/palworld/rcon/rcon -a "127.0.0.1:25575" -p password "Shutdown 5 shutdown "
                sleep 5;;
        *)
                echo "start | stop"
esac

```
</details>



# サービスの登録 
## ユーザー固有の`.service`ファイルの作成
<details><summary>知識</summary>

ユーザー固有のサービスにするためには、以下の2つが必要になります。

- 該当のユーザーにsshでログインができること 
- `~/.config/systemd/user/"service-name".service`のユニットファイル

</details>

### ユニットファイル
今回は以下のファイルを作成しました。
必要に応じて所有者を変更しておいてください。
`~/.config/systemd/user/palworld-dedicated.service `

<details><summary>ユニットファイル例</summary>

```unit-file
[Unit]
Description=Palworld Dedicated Server

[Service]
WorkingDirectory=/home/palworld/Steam/steamapps/common/PalServer/
ExecStart=/bin/bash /home/palworld/Steam/steamapps/common/PalServer/boot.sh start
ExecStop=/bin/bash /home/palworld/Steam/steamapps/common/PalServer/boot.sh stop
TimeoutStartSec=300
Restart=always
RemainAfterExit=yes

[Install]
WantedBy=default.target
```

:::note
ユーザーレベルのユニットファイルでは、通常のユニットファイルとは使えるディレクティブが違います。
基本的には同じものが使えますが、`User`や`Group`が使えません。
:::

</details>

:::note warn
もし、すでにサービスとして`/etc/systemd/system/hogehoge.service`を登録しているのであれば、解除をしておいてください。
:::

## サービスの起動
### 知識
対象のユーザーにsshでログインしてから実行してください。
ユーザー固有のサービスの起動には`sudo`をつける必要がありません。
その代わりに`--user`をつけて実行する必要があります。


:::note warn
`su - hogehoge`では失敗するため注意が必要です。
おそらく下記のエラーで弾かれます。
`Failed to connect to bus: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined (consider using --machine=<user>@.host --user to connect to bus of other user)`
:::

### 起動
サービスファイルを再読み込みします。
```
systemctl --user daemon-reload 
```

サービスを起動します
```
systemctl --user start palworld-dedicated.service
```

一度状態を確認します。
```
systemctl --user status palworld-dedicated.service
```

<details><summary>結果</summary>

> palworld@palworld:~$ systemctl --user status palworld-dedicated.service 
> ● palworld-dedicated.service - Palworld Dedicated Server
>     Loaded: loaded (/home/palworld/.config/systemd/user/palworld-dedicated.service; enabled; vendor preset: enabled)
>     Active: active (exited) since Mon 2024-02-05 11:01:16 JST; 4h 50min ago
>    Process: 10005 ExecStart=/bin/bash /home/palworld/Steam/steamapps/common/PalServer/boot.sh start (code=exited, status=0/SU>
>   Main PID: 10005 (code=exited, status=0/SUCCESS)
>      Tasks: 40 (limit: 38376)
>     Memory: 6.1G
>        CPU: 4h 6min 35.718s
>     CGroup: /user.slice/user-1001.slice/user@1001.service/app.slice/palworld-dedicated.service
>             ├─10008 tmux new-session -s palworld-server -d "/home/palworld/Steam/steamapps/common/PalServer/PalServer.sh -use>
>             ├─10009 /bin/sh /home/palworld/Steam/steamapps/common/PalServer/PalServer.sh -useperfthreads -NoAsyncLoadingThrea>
>             └─10018 /home/palworld/Steam/steamapps/common/PalServer/Pal/Binaries/Linux/PalServer-Linux-Test Pal -useperfthrea>
>
>Feb 05 11:01:16 palworld systemd[786]: Started Palworld Dedicated Server.

</details>



また、`tmux`のセッションを確認してみます。

```
tmux ls
```
<details><summary>結果</summary>

> palworld@palworld:~$ tmux ls
> palworld-server: 1 windows (created Mon Feb  5 11:01:16 2024)

</details>

tmuxのセッションにアタッチする際は以下
デタッチは`ctlr+b`→ `d`
```
tmux a palworld-server
```

<details><summary>結果</summary>

> Shutdown handler: initalize.
> Increasing per-process limit of core file size to infinity.
> 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 2 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.
</details>

問題がなければ一度サービスを停止します。

```
systemctl --user stop palworld-dedicated.service
```

# cronの登録
再起動したい時間に合わせて`cron`を記載してください。
下記は11時、23時に再起動する`cron`です。

```shell
0 11 * * * XDG_RUNTIME_DIR=/run/user/$(id -u) systemctl --user restart palworld-dedicated.service 
0 23 * * * XDG_RUNTIME_DIR=/run/user/$(id -u) systemctl --user restart palworld-dedicated.service 
```

:::note
`XDG_RUNTIME_DIR=/run/user/$(id -u) `を記載しないとエラーを吐いて、再起動をスキップします。
:::

syslogからログを確認するか、`systemctl --user status`からアップタイムを確認することで再起動されたかを確認することが出来ます。


# 戯言
cronではsudoを使えない、というところか模索が始まりました。
セキュリティリスクを減らす観点からrootログインを無効化したかったため、サービスをユーザーレベルに落とし込むことで解決しました。
検証はしていませんが、tmuxを経由せず、サーバー起動のシェルを直接叩いて、サービスをワンショットにすることでoncalenderによる再起動をする、というのがシンプルな解決方法な気がします。
cronに`XDG_RUNTIME_DIR=/run/user/$(id -u)`を記載する必要がある、という情報もいろんな先駆者が記事を残してくれていたおかげですぐにたどり着くことができました。
ありがたい限りです。