setup-config.yaml の解説¶
(2枚目) Resonite Advent Calendar 2024 10日目の記事です
この記事では Google Cloud で resonite ヘッドレスサーバ を構築する で ヘッドレスサーバ を構築する際に指定する元となる setup-config.yaml を生成するためのテンプレート setup-config.yaml.template について解説します
setup-config.yaml とは¶
手順で python gce_cloudinit_yaml_generator.py を実行すると生成されるファイルです。 ユーザ名、パスワードなどを記載した personal-information.json を元に setup-config.yaml.template から生成されます
personal-information.json (再掲)
{
"HEADLESS_PASSWORD": "フレンド欄の resonite に /headlessCode とメッセージを送って返ってくる文字列",
"STEAM_USER": "ヘッドレスサーバ用に作成した Steam アカウント名",
"STEAM_PASSWORD": "Steam アカウントのパスワード",
"HEADLESS_USER": "ヘッドレスサーバ用に作成した resonite ユーザ名"
}
setup-config.yaml は GCE インスタンスを作成するコマンド gcloud compute instances create の引数として --metadata-from-file=user-data=${SETUP_RESONITE_HEADLESS_SERVER_SCRIPT} のように指定しています。
cloud-init という仕組みを利用して GCE インスタンスの初回起動時にだけ setup-config.yaml の記述内容が実行されます
setup-config.yaml.template の全体は下記の通りです
#cloud-config
timezone: Asia/Tokyo
locale: ja_JP.utf8
bootcmd:
- mkdir -p /var/lib/resonite/
- useradd -s /bin/bash -d /home/%%{USER} -m %%{USER}
write_files:
- path: /etc/systemd/system/resonite-headless.service
permissions: 0644
owner: root
content: |
[Unit]
Description=Resonite Headless Server
After=network.service
[Service]
Type=forking
# Restart=always
WorkingDirectory=/home/%%{USER}
ExecStart=/bin/bash /var/lib/resonite/start-resonite-headless.bash
ExecStop=/usr/bin/tmux kill-server
ExecStop=/usr/bin/rm -rf /home/%%{USER}/.local/share/Steam/steamapps/common/Resonite/Headless/Cache/Cache/
User=%%{USER}
Group=%%{USER}
[Install]
WantedBy=multi-user.target
- path: /var/lib/resonite/start-resonite-headless.bash
permissions: 0644
owner: root
content: |
#!/bin/bash
export INSTANCE_NAME=$(curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/name 2>/dev/null)
export HEADLESS_CONFIG_SECRET=${INSTANCE_NAME}
export RESONITE_HEADLESS_DIR="/home/%%{USER}/.local/share/Steam/steamapps/common/Resonite/Headless"
export HEADLESS_CONFIG_FILE=${RESONITE_HEADLESS_DIR}/Config/Config.json
install -d -m 0755 -o %%{USER} -g %%{USER} ${RESONITE_HEADLESS_DIR}/Logs
install -d -m 0755 -o %%{USER} -g %%{USER} ${RESONITE_HEADLESS_DIR}/Config
gcloud secrets versions access latest --secret ${HEADLESS_CONFIG_SECRET} > ${HEADLESS_CONFIG_FILE}
/usr/games/steamcmd +login %%{STEAM_USER} +app_update 2519830 validate +exit
tmux new-session -d "dotnet ${RESONITE_HEADLESS_DIR}/Resonite.dll -HeadlessConfig ${HEADLESS_CONFIG_FILE} -Logs ${RESONITE_HEADLESS_DIR}/Logs"
- path: /home/%%{USER}/.tmux.conf
permissions: 0644
owner: %%{USER}
content: |
# cancel the key bindings for C-b
unbind C-b
# set prefix key
#set -g prefix C-b
# reduce delay of key stroke
set -sg escape-time 1
# begin index of window from 1
set -g base-index 1
# begin index of pane from 1
setw -g pane-base-index 1
# rireki
set-option -g history-limit 500000
# reload tmux config file
bind r source-file ~/.tmux.conf \; display "Reloaded!"
# set prefix key
set-option -g prefix C-k
unbind-key C-b
bind-key C-k send-prefix
# split the pane with a pipe in a vertical
bind v split-window -h
# split the pane with a pipe in a transverse
bind w split-window -v
# move between the panes in the key bindings for vim
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
bind -r C-h select-window -t :-
bind -r C-l select-window -t :+
# resize the pane in the key bindings for vim
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
# use a 256-color terminal
set -g default-terminal "screen-256color"
# set the color of the status bar
set -g status-fg white
set -g status-bg black
# set status bar
## set the left panel
set -g status-left-length 40
set -g status-left "#[fg=green]Session: #S #[fg=yellow]#I #[fg=cyan]#P"
## set the right panel
set -g status-right-length 100
set -g status-right '#[fg=cyan][%Y-%m-%d(%a) %H:%M]'
## set the refresh interval (default 15 seconds)
set -g status-interval 60
## center shifting the position of the window list
set -g status-justify centre
## enable the visual notification
setw -g monitor-activity on
set -g visual-activity on
## display the status bar at the top
set -g status-position top
# set the copy mode
## use the key bindings for vi
setw -g mode-keys vi
source /usr/share/powerline/bindings/tmux/powerline.conf
run-shell "powerline-daemon -q"
- path: /var/lib/resonite/setup-resonite-headless-server.bash
permissions: 0644
owner: root
content: |
#!/bin/bash
set -o pipefail
declare -r command_log="/root/command.log"
declare -a command_lines=(
'sudo apt update'
'sudo apt install -y software-properties-common'
'sudo add-apt-repository -y multiverse'
'sudo dpkg --add-architecture i386'
'sudo add-apt-repository -y ppa:dotnet/backports'
'sudo apt update'
'echo steam steam/license note "" | sudo debconf-set-selections'
'echo steam steam/question select "I AGREE" | sudo debconf-set-selections'
'sudo apt install -y lib32gcc-s1 curl libopus-dev libopus0 opus-tools libc-dev tmux dstat powerline gnupg ca-certificates vim nano dotnet-runtime-9.0 steamcmd'
'curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh'
'sudo bash add-google-cloud-ops-agent-repo.sh --also-install'
'sudo apt autoremove -y'
'sudo -u %%{USER} /usr/games/steamcmd +login %%{STEAM_USER} %%{STEAM_PASSWORD} +app_license_request 2519830 +app_update 2519830 -beta headless -betapassword %%{HEADLESS_PASSWORD} validate +exit'
'export RESONITE_HEADLESS_DIR="/home/%%{USER}/.local/share/Steam/steamapps/common/Resonite/Headless"'
'systemctl daemon-reload'
'systemctl --now enable resonite-headless.service'
'sudo touch ~/INITIALIZED'
)
timedatectl set-timezone Asia/Tokyo
get_datetime="date +'%Y-%m-%d %H:%M:%S'"
number_of_erros=0
function err() {
status=$?
lineno=$1
func_name=${2:-main}
date_time=$(eval ${get_datetime})
err_str="${date_time} ERROR: ${SCRIPT}:${func_name}() returned non-zero exit status ${status} at line ${lineno}. Message: ${command_result}"
echo ${err_str} >> ${command_log}
let number_of_erros++
exit
}
function finally() {
if [ ${number_of_erros} = 0 ]; then
finalize_message="Succeeded."
else
finalize_message="Failure: Number of errors is ${number_of_erros}"
fi
date_time=$(eval ${get_datetime})
echo -e "${date_time} ${finalize_message}" >> ${command_log}
}
# Start error handling
trap 'err ${LINENO[0]} ${FUNCNAME[1]}' ERR
trap finally EXIT
# Start acquiring logs
date_time=$(eval ${get_datetime})
echo -e "${date_time} Start Scrpt" > ${command_log}
# Execute main processing
for command in "${!command_lines[@]}"; do
command_result=$(
eval ${command_lines[command]} 2>&1
)
date_time=$(eval ${get_datetime})
echo ${date_time} Executed: ${command_lines[command]} >> ${command_log}
done
exit 0
runcmd:
- /bin/bash /var/lib/resonite/setup-resonite-headless-server.bash
以降、 setup-config.yaml.template をブロックごとに解説します
setup-config.yaml.template の解説¶
各モジュール(bootcmd, write_files, runcmd)は優先順位が存在しており、bootcmd, write_files, runcmd の順番で優先実行されます
トップ行¶
#cloud-config ⇒ cloud-init のファイルという宣言です
timezone: Asia/Tokyo ⇒ 時間の表示を日本時間にします
locale: ja_JP.utf8 ⇒ システムの環境を日本語環境にします
bootcmd ブロック¶
今回使っているモジュールの中で、実行される優先順位が一番高いブロックです
bootcmd:
- mkdir -p /var/lib/resonite/ ⇒ Unit ファイルを格納するディレクトリを作成します
- useradd -s /bin/bash -d /home/%%{USER} -m %%{USER} ⇒ Google Cloud Shell のユーザで、GCE インスタンスのユーザを作成します
write_files ブロック¶
以下 4 ファイルを作成します
- resonite-headless.service:
システムが起動する際に毎回実行される Unit ファイルです。 ヘッドレスサーバ の起動/停止を制御します
- start-resonite-headless.bash:
resonite-headless.service が呼び出している ヘッドレスサーバ を起動するスクリプトです
- .tmux.conf:
ヘッドレスサーバ は tmux のセッションとして起動しており、その tmux の設定ファイルです
- setup-resonite-headless-server.bash:
runcmd モジュールで最後に実行されるスクリプトです(つまりインスタンス初回起動時のみ実行されます)
resonite-headless.service 行
- path: /etc/systemd/system/resonite-headless.service ⇒ このパスに以下のファイルを作成します
# ファイルのパーミッションとオーナーを指定します
permissions: 0644
owner: root
# content 以降がファイルに書き込まれる内容です
content: |
[Unit]
Description=Resonite Headless Server
After=network.service
[Service]
Type=forking
# Restart=always
WorkingDirectory=/home/%%{USER}
ExecStart=/bin/bash /var/lib/resonite/start-resonite-headless.bash ⇒ インスタンス起動時に、このスクリプトを実行します。次のブロックで解説します
ExecStop=/usr/bin/tmux kill-server ⇒ Unit が停止する時に実行されます。tmux を停止しますが、実質ここでヘッドレスサーバが停止します
ExecStop=/usr/bin/rm -rf /home/%%{USER}/.local/share/Steam/steamapps/common/Resonite/Headless/Cache/Cache/ ⇒ 上記の直後に実施されます。ヘッドレスのキャッシュを削除します
User=%%{USER}
Group=%%{USER}
[Install]
WantedBy=multi-user.target
start-resonite-headless.bash 行
- path: /var/lib/resonite/start-resonite-headless.bash ⇒ このパスに content 以下のファイルを作成します
permissions: 0644
owner: root
content: |
#!/bin/bash
# インスタンス内部から自身のインスタンス名を取得します(インスタンス名と同名のシークレットにアクセスする為の布石)
export INSTANCE_NAME=$(curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/name 2>/dev/null)
export HEADLESS_CONFIG_SECRET=${INSTANCE_NAME}
export RESONITE_HEADLESS_DIR="/home/%%{USER}/.local/share/Steam/steamapps/common/Resonite/Headless"
export HEADLESS_CONFIG_FILE=${RESONITE_HEADLESS_DIR}/Config/Config.json
# ヘッドレスサーバが使用するディレクトリを作成します
install -d -m 0755 -o %%{USER} -g %%{USER} ${RESONITE_HEADLESS_DIR}/Logs
install -d -m 0755 -o %%{USER} -g %%{USER} ${RESONITE_HEADLESS_DIR}/Config
# ヘッドレスサーバの Config.json をシークレットから生成します(ヘッドレスサーバ起動時に毎回書き換えていることに注意)
gcloud secrets versions access latest --secret ${HEADLESS_CONFIG_SECRET} > ${HEADLESS_CONFIG_FILE}
# steamcmd コマンドでヘッドレスサーバをアップデートします(ヘッドレスサーバ起動時に毎回実行しています)
/usr/games/steamcmd +login %%{STEAM_USER} +app_update 2519830 validate +exit
# tmux からヘッドレスサーバを起動します
tmux new-session -d "dotnet ${RESONITE_HEADLESS_DIR}/Resonite.dll -HeadlessConfig ${HEADLESS_CONFIG_FILE} -Logs ${RESONITE_HEADLESS_DIR}/Logs"
.tmux.conf 行
- path: /home/%%{USER}/.tmux.conf
permissions: 0644
owner: %%{USER}
content: |
# cancel the key bindings for C-b
unbind C-b
# set prefix key
#set -g prefix C-b
# reduce delay of key stroke
set -sg escape-time 1
# begin index of window from 1
set -g base-index 1
# begin index of pane from 1
setw -g pane-base-index 1
# rireki
set-option -g history-limit 500000
# reload tmux config file
bind r source-file ~/.tmux.conf \; display "Reloaded!"
# set prefix key
set-option -g prefix C-k
unbind-key C-b
bind-key C-k send-prefix
# split the pane with a pipe in a vertical
bind v split-window -h
# split the pane with a pipe in a transverse
bind w split-window -v
# move between the panes in the key bindings for vim
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
bind -r C-h select-window -t :-
bind -r C-l select-window -t :+
# resize the pane in the key bindings for vim
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
# use a 256-color terminal
set -g default-terminal "screen-256color"
# set the color of the status bar
set -g status-fg white
set -g status-bg black
# set status bar
## set the left panel
set -g status-left-length 40
set -g status-left "#[fg=green]Session: #S #[fg=yellow]#I #[fg=cyan]#P"
## set the right panel
set -g status-right-length 100
set -g status-right '#[fg=cyan][%Y-%m-%d(%a) %H:%M]'
## set the refresh interval (default 15 seconds)
set -g status-interval 60
## center shifting the position of the window list
set -g status-justify centre
## enable the visual notification
setw -g monitor-activity on
set -g visual-activity on
## display the status bar at the top
set -g status-position top
# set the copy mode
## use the key bindings for vi
setw -g mode-keys vi
source /usr/share/powerline/bindings/tmux/powerline.conf
run-shell "powerline-daemon -q"
setup-resonite-headless-server.bash 行
- path: /var/lib/resonite/setup-resonite-headless-server.bash ⇒ このパスに content 以下のファイルを作成します
permissions: 0644
owner: root
content: |
#!/bin/bash
set -o pipefail
# 本スクリプトログの出力先を指定しています
declare -r command_log="/root/command.log"
# 実行するコマンドを配列にしています
declare -a command_lines=(
'sudo apt update'
'sudo apt install -y software-properties-common'
'sudo add-apt-repository -y multiverse'
'sudo dpkg --add-architecture i386'
'sudo add-apt-repository -y ppa:dotnet/backports'
'sudo apt update'
# steam のインストール時に聞かれる質問をスキップする布石です
'echo steam steam/license note "" | sudo debconf-set-selections'
'echo steam steam/question select "I AGREE" | sudo debconf-set-selections'
# 必要なアプリケーションをインストールします
'sudo apt install -y lib32gcc-s1 curl libopus-dev libopus0 opus-tools libc-dev tmux dstat powerline gnupg ca-certificates vim nano dotnet-runtime-9.0 steamcmd'
# GCE インスタンスのモニタリング用エージェントをインストールします
'curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh'
'sudo bash add-google-cloud-ops-agent-repo.sh --also-install'
'sudo apt autoremove -y'
# steamcmd でヘッドレスサーバをインストールします
'sudo -u %%{USER} /usr/games/steamcmd +login %%{STEAM_USER} %%{STEAM_PASSWORD} +app_license_request 2519830 +app_update 2519830 -beta headless -betapassword %%{HEADLESS_PASSWORD} validate +exit'
'export RESONITE_HEADLESS_DIR="/home/%%{USER}/.local/share/Steam/steamapps/common/Resonite/Headless"'
# 新しく追加した Unit ファイル resonite-headless.service を認識させます
'systemctl daemon-reload'
# resonite-headless.service を即座に有効にし、なおかつシステム起動時に有効にします
'systemctl --now enable resonite-headless.service'
'sudo touch ~/INITIALIZED'
)
timedatectl set-timezone Asia/Tokyo
get_datetime="date +'%Y-%m-%d %H:%M:%S'"
# エラーハンドリング行です
number_of_erros=0
function err() {
status=$?
lineno=$1
func_name=${2:-main}
date_time=$(eval ${get_datetime})
err_str="${date_time} ERROR: ${SCRIPT}:${func_name}() returned non-zero exit status ${status} at line ${lineno}. Message: ${command_result}"
echo ${err_str} >> ${command_log}
let number_of_erros++
exit
}
# エラーハンドリングブロックの最後に実行する行です
function finally() {
if [ ${number_of_erros} = 0 ]; then
finalize_message="Succeeded."
else
finalize_message="Failure: Number of errors is ${number_of_erros}"
fi
date_time=$(eval ${get_datetime})
echo -e "${date_time} ${finalize_message}" >> ${command_log}
}
# Start error handling
trap 'err ${LINENO[0]} ${FUNCNAME[1]}' ERR
trap finally EXIT
# Start acquiring logs
date_time=$(eval ${get_datetime})
echo -e "${date_time} Start Scrpt" > ${command_log}
# 配列 command_lines の要素であるコマンドを実行していきます。エラーが出たところで止まり、ログにエラー出力します
# Execute main processing
for command in "${!command_lines[@]}"; do
command_result=$(
eval ${command_lines[command]} 2>&1
)
date_time=$(eval ${get_datetime})
echo ${date_time} Executed: ${command_lines[command]} >> ${command_log}
done
exit 0
runcmd ブロック¶
最後に実行されるブロックです。 必要なパッケージをインストールしたり、Unit を作成する為の setup-resonite-headless-server.bash を実行しています
- /bin/bash /var/lib/resonite/setup-resonite-headless-server.bash
その他¶
この記事は、 ヘッドレスサーバ がどのように起動しているのかを知って頂きたく執筆しました。 自身でカスタマイズする際の手助けになればと思います。
また、本手順は cloud-init を利用するクラウドリソースを前提としていますが、自宅等の物理サーバで運用する場合も役に立つ部分があるかもしれません。
cloud-init を使わずとも、resonite-headless.service や start-resonite-headless.bash は Linux システムを利用する場合は有効な手段だと考えています。
もっと良い方法があるよ!という方がいらっしゃれば是非フィートバックをお願いします。
明日の記事 へ続きます。