[Linux] Satisfactory の Steam Dedicated Server を構築する

# はじめに
Satisfactory の Linux Dedicated Server を構築する手順です。

Steam の Dedicated Server とはゲーム本体を購入せずとも、マルチプレイ用ゲームサーバを構築する仕組みです。Windows または Linux 用が配布されていますが、今回は Linux 向けの構築手段に絞って記載します。
また Dedicated Server は Satisfactory に限らず、Ark: Servival Evolved や Valheim といった Steam ゲームでも共通の手順でセルフホスティングが可能です。

# 構築環境
私の環境は以下の通りです。
| スペック   | 動作環境                | 注釈       | 
| ---------- | ----------------------- | ------------- | 
| OS         | Ubuntu Server 22.04 LTS | 他の OS でも、手順を読み替えれば動きます| 
| CPU        | 4コア vCPU              | CPU は 30% 台で推移しているので、もっと少なくても余裕がありそうです         | 
| メモリ     | 10GB MEM               | 公式では8GBが必須とされていますが、私の環境では7〜9GBの使用率で推移しているので10GB割り当てています  | 
| ストレージ | 50GB SSD                | ゲームサーバのデータは 2022/12 時点で約6.9GBなので、30GB くらいでも足ります | 

また下記の状態が前提になりますが、これは環境によって異なるためここでは解説しません。

* 公開できるグローバル IP アドレスがある
* ルーター下のサーバであれば IP アドレスが固定されており、Satisfactory で利用される 15000, 15777, 7777 (いずれもUDP) ポートをルータの IP マスカレード機能などでサーバが公開されている

もしこれからサーバを借りるのであれば [ConoHa VPS](https://www.conoha.jp/vps/) がオススメです。仮想マシンのスナップショットが取れて便利です。

もしも自宅サーバで直接ポートを開放したくない方は、ngrok や Cloudflare Tunnel というものもあります。
* [Cloudflare Tunnel でおうちサーバを公開する](https://zenn.dev/grarich/articles/2735b5b1b2f91f)
* [Cloudflare の ngrok的なサービスがあるらしい ⇒ $ cloudflared tunnel --url localhost:8080 といった使い方 - Qiita](https://qiita.com/youtoy/items/3d312552f08a729bb5bb)

# 構築手順
ユーザ名 `steamusr` でサーバが実行される手順を解説しています。

### 1. steamcmd をインストール
Steam をインストール
```
sudo apt install -y lib32gcc-s1 steamcmd
```
利用規約について聞かれるので、問題なければ「2」を入力する

シンボリックリンクを追加
```Bash
sudo ln -s /usr/games/steamcmd /home/steamusr/steamcmd
```

### 2. Satisfacroty サーバのダウンロード
steamcmd の起動
```Bash
steamcmd
```

インストールフォルダの作成
```
Steam> force_install_dir /home/steamusr/Steam/steamapps/common/Satisfactory
```

匿名ユーザーでログイン
```
Steam> login anonymous
```

Satisfactory の Dedicated Server をダウンロード
※1690800 は Satisfactory の App ID を指しています ([SteamDB](https://steamdb.info/app/1690800/))。初回ダウンロードは数十分ほど掛かります。
```
Steam> app_update 1690800 validate
```
steamcmd から抜ける
```
Steam> exit
```

### 3. Systemd に登録
最近の Linux は systemd でサービスが管理されていますので、ゲームサーバもここから起動させましょう。
`satisfactory-dedicated.service` を作成します。
```Bash
sudo vi /etc/systemd/system/satisfactory-dedicated.service
```
以下を入力して保存してください。
```
[unit]
Description=Satisfactory Dedicated Server
Wants=network-online.target
After=syslog.target network.target nss-lookup.target network-online.target

[Service]
ExecStart=/home/steamusr/Steam/steamapps/common/Satisfactory/FactoryServer.sh
LimitNOFILE=100000
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s INT $MAINPID
User=steamusr
Group=steamusr
Restart=always
TimeoutStartSec=300

[Install]
WantedBy=multi-user.target
```
daemon をリロードしてサーバを起動させましょう。自動起動もするようにします。

```Bash
# デーモンの再起動
sudo systemctl daemon-reload

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

# サーバの起動
sudo systemctl start satisfactory-dedicated.service
```
ここまでで、ポート開放が問題なく行えていれば接続できるはずです。
できなければ Firewalld などを確認してみてください。

また以下のコマンドで停止操作などが行なえます。
```Bash
# ゲームサーバを停止させたいとき
sudo systemctl stop satisfactory-dedicated.service

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

# ゲームサーバが出力したログを確認するとき
sudo journalctl -u satisfactory-dedicated.service
```
## セーブデータの移行とバックアップ
もし先行して誰かの PC をホストにマルチプレイをしていた場合、セーブデータを Dedicated Server に移管したい場合があると思います。

Windows では以下のパスに保存されています。ホストになっていたプレイヤーからセーブデータを貰ってください。
```
C:\Users\(Username)\AppData\Local\FactoryGame\Saved\SaveGames\(SteamのID)
```
上記パスはエクスプローラーに `%localappdata%\FactoryGame\Saved\SaveGames\` を入力すれば開くことができます。

また Dedicated Server 側では以下のパス配下に `.sav` 形式のセーブデータを保存します。
```
~/.config/Epic/FactoryGame/Saved/SaveGames/server
```

## あるとより便利に
ここまででもう既にゲームはプレイできる状態になっていますが、より便利なサーバ運用を記載します。

### ①自動アップデート
Satisfactory は頻繁にアップデートされています。
クライアント側は自動でアップデートが入りますが、サーバ側では手動でアップデートを行わないとゲームに接続することができなくなります。
![server-error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/a0c4fd2b-63b1-9eb7-181d-805d3c198a80.png)
そこでシェルスクリプトを用いて steamcmd の定期的なアップデートを実行しましょう。

以下のファイルを作成します。
```Bash
vi ~/auto-update.sh
```
変数は環境に応じて書き換え、下記のシェルスクリプトをペーストしてください。
```Bash
#!/bin/sh

Steamcmd="/usr/games/steamcmd"
install_dir="/home/steamusr/Steam/steamapps/common/Satisfactory"
AppID="1690800" #Satisfactory
service="satisfactory-dedicated.service"

date +"%Y/%m/%d %p %I:%M:%S"

echo "# Check current build number."
OLD_Build=`${Steamcmd} +force_install_dir ${install_dir} +login anonymous +app_status ${AppID} +quit | grep -e "BuildID" | awk '{print $8}' 2> /dev/null`
echo "Old BuildID: ${OLD_Build}"

echo "# Updating the game server."
${Steamcmd} +force_install_dir ${install_dir} +login anonymous +app_update ${AppID} validate +quit >/dev/null 2>&1

echo "# Check build number after update."
NEW_Build=`${Steamcmd} +force_install_dir ${install_dir} +login anonymous +app_status ${AppID} +quit | grep -e "BuildID" | awk '{print $8}' 2> /dev/null`
echo "New BuildID: ${NEW_Build}"

# Check version differences
if [ "$OLD_Build" -eq "$NEW_Build" ]; then
    echo "No game updates available."
elif [ "$OLD_Build" -gt "$NEW_Build" ]; then
    echo "The game has a new BuildID ${NEW_Build}. Service will be restarted."
    systemctl stop $service
    systemctl start $service
    systemctl status $service
else
    echo "error."
fi
```
動作させてみます。
```Bash
chmod +x auto-update.sh
sudo ./auto-update.sh
```
このような出力が得られれば、問題なく動作しています。
```Bash
steamusr@satisfactory:~$ ./auto-update.sh
2022/01/01 AM 00:00:00
# Check current build number.
Old BuildID: 10115591
# Updating the game server.
# Check build number after update.
New BuildID: 10115591
No game updates available.
steamusr@satisfactory:~$ 
```
もしゲームのアップデートがあればサービスの再起動が行われます。
#### Cron 登録
Cron に登録してこのスクリプトを定期的に動作させましょう。ワンライナーで登録します。
```
(sudo crontab -u root -l; echo "0 */3 * * * /home/steamusr/auto-update.sh  >> /home/steamusr/auto-update.log 2>&1") | sudo crontab -u root -
```
root ユーザで実行されます。実行頻度は好みですが、例では3時間に一度になっています。また `/home/steamusr/auto-update.log` に実行結果が出力されます。

このように登録されていれば OK。
```
steamusr@satisfactory:~$ sudo crontab -u root -l
0 */3 * * * /home/steamusr/auto-update.sh  >> /home/steamusr/auto-update.log 2>&1
```

### ②Mackerel で死活監視
サーバやプロセスが死んだら Discord に通知が欲しいですよね。株式会社はてなが提供する Mackerel を利用します。通知だけであれば Freeプランで十分です。

1. まず通知を送りたい Discord のテキストチャンネルを開き「チャンネルの編集」→「連携サービス」→「ウェブフック」→「新しいウェブフック」で Webhook URL を作成し、コピーします。
![Discord 2022-12-09 17.50.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/6570d6c2-6791-882c-c6b9-920b1c46734d.png)
![Discord 2022-12-09 17.53.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/b28db5f7-2323-c0c4-4484-a098af14f1af.png)
2. [Mackerel に登録](https://mackerel.io/signup)してダッシュボードにログインします
3. まず「新規ホストの登録」で表示された OS 毎のインストールコマンドを実行します。ダッシュボード上の Hosts にサーバが表示されます。
![新規ホストの登録 · Google Chrome 2022-12-09 17.45.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/2db21578-6825-618f-268a-f521b0f632d8.png)
4. ダッシュボードの Channels から「通知グループ／通知チャンネルを追加」で Slack を選択します
5. 先ほど Discord からコピーした URL の末尾に `/slack` をつけ、`https://discord.com/api/webhooks/XXXX/XXXX/slack` となっている URL をダッシュボードに入力します。
Discord には Slack Webhook 互換があり、この形式にすることで Slack 形式の Webhook を受け取れるようになります。
![通知グループ／通知チャンネルを追加 - Google Chrome 2022-12-09 17.58.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/0690d2d7-6d1a-e9c3-cb1d-c3efb3a48084.png)
6. 「アラート通知」と「ホストステータス変更」をオンにして「作成」します。
![通知グループ／通知チャンネルを追加  - Google Chrome 2022-12-09 18.03.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/44324057-edea-e76e-f7b4-11bb4a1a1573.png)
7. プロセスが死んでも通知して欲しいので、mackrel のチェック監視項目を追加します。
サーバで以下のコマンドを実行します。
    ```Bash
    # mackerel 公式チェックプラグイン集をインストール
    # rpm パッケージの場合は sudo yum install mackerel-check-plugins
    sudo apt-get install mackerel-check-plugins
    
    # UE4Server-Linux というプロセスが起動しているかをチェックするように mackerel-agent.conf の2行目に追記
    sudo sed -i -e "2i [plugin.checks.check_satisfactory]\ncommand = [\"/usr/bin/check-procs\", \"--pattern\", \"UE4Server-Linux\"]\n" /etc/mackerel-agent/mackerel-agent.conf
    
    # mackrel-agent を再起動
    sudo service mackerel-agent restart
    ```
8. Mackrel のダッシュボードに戻り、Hosts からサーバを選択して check_satisfactory が動作していれば監視ができています。
![satisfactory · game-srv - Google Chrome 2022-12-09 18.21.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/b3be1b63-a50b-a438-2139-79fe631d9b7b.png)

これで Satisfactory のプロセスが死んだり、サーバの電源が落ちると Discord に通知が来るようになります。
![Discord 2022-12-09 18.28.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206465/6871c4ba-93d2-f55a-bbab-dad4859a0fbe.png)
また Mackerel に自身のアドレスなどを登録しておけばメールでもサーバの異常を確認できて便利です。

# 謝辞
この手順は以下のページを一部参考にさせていただいています。
* [Satisfactory専用サーバーの立て方（UbuntuServer ） - Qiita](https://qiita.com/cat-craft/items/2a2e6dee014df5f5a1a0)