前書き
友人をTerrariaの沼に沈め、また、自身もTerrariaに囚われてしまった責任を取ってサーバーを立てることにしたので、その際の作業をメモしておきます。
構成
- PC:Raspberry Pi 4 Model B/4GB element14
- OS:Ubuntu 23.04
- ストレージ:microSD(32GB)
自分のメインPCをずっと起動しておくのは憚られたので、消費電力の低さを買ってラズパイを利用することにしました。
ただ、Ubuntuのほうが記事もパッケージも多いので、OSはUbuntu Desktop 23.04を選択しました。GUI操作が欲しかったのでUbuntu ServerではなくUbuntu Desktopにしています。
OSのインストール
microSDを差し込めるPCに公式サイト内の配布ページからダウンロードしたRaspberry Pi Imagerをインストール。OSとしてUbuntuを、インストールディスクとしてmicroSDを選択してインストールします。
microSDのフォーマットはexFATではなくFAT32でなくてはならないことに注意しましょう。
起動後の設定
とりあえず$ sudo apt update
と$ sudo apt upgrade
を実行。
個人用の設定
必須ではないですが自分がいつもする設定も載せておきます。
-
電源プランを変更
設定アプリの"Power"から"Power Saving Option"の"Screen Blank"を"Never"に -
vimのインストールと設定
$ sudo apt install vim
$ vim ~/.vimrc
$ source ~/.vimrc
set backspace=indent,eol,start set ambiwidth=double
set ignorecase
set smartcase
set wrapscan
set incsearch
set hlsearch
set noerrorbells
set shellslash
set showmatch matchtime=1
set cinoptions+=:0
set cmdheight=2
set laststatus=2
set history=10000
set expandtab
set shiftwidth=2
set softtabstop=2
set tabstop=2
set guioptions+=a
set showmatch
set smartindent
set title
set number
set clipboard=unnamed,autoselect
- GNU Screenのインストールと設定
後々sshでサーバーを管理するために導入
sudo apt install screen
vim .screenrc
autodetach on
deflog off
logfile "/home/$USER/screen-%Y%m%d-%n.log"
logfile flush secs
hardcopydir "/home/$USER/screen-hardcopy.%n"
defutf8 on
defencoding utf8
encoding utf8 utf8
hardstatus alwayslastline "%{= rw} %H %{= wk}%-Lw%{= bw}%n%f* %t%{= wk}%+Lw %{= wk}%=%{= gk} %y/%m/%d %c "
ローカルIPアドレスの固定
実際にサーバーを立てたとして、自分は別のPCからローカルIP経由でサーバーに参加することになります。
そのため、ローカルIPアドレスは固定しておきたい。
やり方は主にこちらの記事を参考にさせて頂きました。
まずはnet-toolsのインストール
$ sudo apt install net-tools
次に、ネット接続を確認
$ ip -br -c addr show
(ここでネットワークインターフェースも確認しておきます。)
そして、設定ファイルの編集に入ります。
デフォルトの設定ファイルをコピーし、元ファイルの拡張子を変えておきます。
$ cp /etc/netplan/01-use-network-manager.yaml /etc/netplan/99-eth0-init.yaml
$ mv /etc/netplan/01-use-network-manager.yaml_old
そして、99-eth0-init.yaml
をvimで編集していきます。
network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: false
dhcp6: false
addresses: [192.168.10.220/24]
nameservers:
addresses: [192.168.10.1]
routes:
- to: default
via: 192.168.10.1
インデントが大事なファイルなので、そこは注意しておきましょう。インデントエラーが出まくる場合は、expandコマンドでTab文字とスペースの置換を試してみてください。
(自分は version: 2 の「:」の後ろにあるスペースを忘れてエラーが出ていました。)
保存できたら、設定を反映します。
$ sudo netplan apply
もう一度$ ip -br -c addr show
を試して、設定したIPになっていればOKです。
ポート開放
Terrariaのサーバーアプリは自動でポートを開けてくれるはずですが、一応手動で開放します。
こちらの記事を主に参考にさせて頂きました。
まずはファイアウォールを有効化
$ sudo ufw enable
次に開放するポート(Terrariaのデフォルトは7777)を指定
$ sudo ufw allow 7777
最後に$ sudo ufw reload
で適用、$ sudo ufw status
で確認します。
環境によってはルーターから設定する必要がある場合もあるかも?
以降、sshやsambaのように外部と通信をする際には適宜ポートを開放する必要があることを忘れずに。
これで準備できたので、次からサーバーアプリを入れていきます。
Terrariaサーバーを立てる
当初はこちらの記事に従って公式のアプリを使用する予定だったのですが、TerrariaServer.bin.x86_64
を実行したところsyntax error: word unexpected (expecting ")")
やELF not found
といったエラーに阻まれ、サーバーを立てられませんでした。
ラズパイはCPUがARMなので、それが原因なんじゃないかと思います。
途方に暮れていたところ、ラズパイでTerrariaサーバーを立てている記事を発見!
非公式サーバークライアントのTShockなら問題なく動くとのことで、こちらを利用することに。
まずはzip解凍のためにunzipをインストール
$ sudo apt install unzip
サーバープログラムの実行に必要だそうなので、monoをインストール(要らなかったかも...?)
$ apt-get install mono-complete
TShockの実行に必要と言われたので .Net 6をインストール
$ sudo apt install dotnet6
セキュリティの関係でサーバー実行用のユーザーを用意したほうが良いとのことで、ユーザーを作成(今回は安直にterrariaという名前に)
$ sudo adduser terraria
ユーザーの切り替え
$ su - terraria
テラリアのサーバーを置くディレクトリを作成
$ mkdir terraria
$ cd terraria
これで事前の準備が済んだので、TShockをダウンロードしていきます。
公式の配布ページから最新版のリンクをコピーして、ダウンロード
$ curl -LO https://github.com/Pryaxis/TShock/releases/download/v5.2.0/TShock-5.2-for-Terraria-1.4.4.9-linux-arm64-Release.zip
解凍していきます
$ unzip TShock-5.2-for-Terraria-1.4.4.9-linux-arm64-Release.zip
$ tar -xvf TShock-Beta-linux-arm64-Release.tar
あとは$ ./TShock.Server
でサーバーを実行し、案内に従ってワールドの作成・選択やポート・パスワードの設定をすればOKです!
追加の設定
-
既にワールドデータが別PCにある場合は、
/home/$USER/.local/share/Terraria/Worlds/
に.wldファイルを置けば認識されます。 -
tshock/config.json
を弄ることでサーバーの設定を変更できます。(こちらの記事を参考にさせて頂きました。)
デフォルトでは"SpawnProtection"
がtrueになっていたため、拠点の整備がしづらかったです。
気の知れた友人とやる前提なら、falseで良いので変更しました。
botの作成
他の人を招くには、グローバルIPを相手に知らせる必要があります。しかし、これを固定するのはお金がかかります。
要は現在のグローバルIPさえ伝わればいいのですから、discordのbotを作って、コマンドを叩けばグローバルIPを返すようにすればいいのではと思い、botを作ることに。
(かなりの素人考えなので、セキュリティの問題を無視しまくっています。自己責任で。)
自分のグローバルIPを知るには、以下のコマンドを叩けばOKです。
$ curl ifconfig.co
botについては、書き慣れたpythonで書くことに。そのため、discord.pyをインストールします。
$ pip install discord.py==1.7.3
バージョンを最新のものにすると過去のコードが使えなかったので、過去のバージョンを指定しました(またしてもセキュリティ軽視)。
しかし、ここでexternally-managed-environment
というエラーが発生。
こちらの記事を参考に、~/.pip/pip.conf
を変更して対処しました。
[global]
break-system-packages = true
discordのbot作成のため、discord公式の開発者サイトにてbotを作成し、トークンの取得とサーバーへの導入を済ませます。
あとはちゃっとコードを書いて、実行!
import asyncio
import discord
from discord.ext import commands
import subprocess
bot = commands.Bot(command_prefix="!")
token = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
#botの読み込み直後に実行
@bot.event
async def on_ready():
print('My bot ready!')
print('discord.py version is ' + discord.__version__)
adress = subprocess.run(["curl","ifconfig.co"],shell=True,text=True,stdout=subprocess.PIPE).stdout
activity = discord.Activity(name=adress, type=discord.ActivityType.playing)
await bot.change_presence(activity=activity)
#関数作成
@bot.command()
async def terraria(ctx):
"""IPアドレスを表示"""
adress = subprocess.run(["curl","ifconfig.co"],shell=True,text=True,stdout=subprocess.PIPE).stdout
await ctx.send(adress)
activity = discord.Activity(name=adress, type=discord.ActivityType.playing)
await bot.change_presence(activity=activity)
bot.run(token)
毎回コマンドを叩いてもらわなくても済むように、アクティビティにIPを表示しておきます。
ただ、念のためにコマンドも用意し、コマンドが叩かれたらアクティビティも更新するようにしました。
更なる機能拡張
botを作ったとなると、botから現在のプレイヤー数が分かれば便利だなあと感じ、実装することに。
当初はGNU screenに命令を送ってログを読むという力技で解決しようとしていましたが、なんとTShockにはAPIがあるようです!(公式サイト)
というわけで、これを使いましょう。
まずは一旦サーバーを止めて、tshock/config.json
を編集します。
"RestApiEnabled"
をtrueに変更し、"RestApiPort": 7878
となっていることを確認します。
APIの通信に7878ポートを使うので、$ sudo ufw allow 7878
で7878ポートを開放します。
$ sudo ufw reload
で適用し、$ sudo ufw status
で確認しました。
設定出来たらサーバーを起動し、こちらのサイトを参考にsuperadminユーザーを作成します。
/user add aoi hoge superadmin
これで、aoiというユーザーをhogeというパスワードでsuperadminとして登録できました。
では、公式サイトを参考にまずはAPIの利用に必要なトークンを取得しましょう。
以下のURLにアクセスします。
http://127.0.0.1:7878/v2/token/create?username=aoi&password=hoge
サーバーを実行しているラズパイからアクセスするなら127.0.0.1:7878で問題ないはずですが、別PCからアクセスするならここはラズパイのローカルIPに変更しましょう。
また、aoi, hoge は自分が設定したsuperadminアカウントのユーザー名とパスワードに変更してください。
トークンさえ取得できたら、http://127.0.0.1:7878/lists/players?token=xxxxxxxxxxxxxxxxxxxxxから現在の参加プレイヤーを確認できます。
後はこの情報をdiscordのbotで扱えるようにすればOKです。
以下の関数を付け加えればOK。
import requests
import json
@bot.command()
async def player(ctx):
"""プレイ中の人数を表示"""
url = requests.get("http://127.0.0.1:7878/lists/players?token=xxxxxxxxxxxxxxxxxxxxx")
text = url.text
data = json.loads(text)
player_list = data["players"].split(",")
print(player_list)
if player_list[0] == "":
await ctx.send("現在0人が遊んでいます")
else:
await ctx.send(f"現在{len(player_list)}人が遊んでいます")
情報がjson形式で送られてくるので、辞書型を思い出しながら実装。
プレイヤーが0人でも空文字列が返ってくるので、人数を返す際に例外処理をしています。
追記
サーバーを立て直したらトークンが無効になることを失念していました。
そのため、正しくは以下のような形になります。
import requests
import json
@bot.command()
async def player(ctx):
"""プレイ中の人数を表示"""
token_url = requests.get("http://127.0.0.1:7878/v2/token/create?username=aoi&password=hoge")
token_text = token_url.text
token_data = json.loads(token_text)
terra_token = token_data["token"]
url = requests.get(f"http://127.0.0.1:7878/lists/players?token={terra_token}")
text = url.text
data = json.loads(text)
player_list = data["players"].split(",")
print(player_list)
if player_list[0] == "":
await ctx.send("現在0人が遊んでいます")
else:
await ctx.send(f"現在{len(player_list)}人が遊んでいます")
bot起動中にサーバーを立て直した場合を考え、関数が実行されるたびにtokenを取得する形にしていますが、短時間に何度も関数を呼び出すと固まってしまいそうです...痛しかゆし...。
サーバーを立て直すたびにbotを再起動するなら、global変数にした方がいいですね。
総括
これにてやりたかったことが全て実現出来ました!
やろうと思えばdiscordから様々な命令が送れるようにもできますが、まあ、流石にこれはやめておきましょう。
sshやsambaの設定についてもまた記事を書くかもしれません。
では。