Repository navigation
Expand file tree
/
Copy pathcs2ctl
More file actions
executable file
·422 lines (390 loc) · 20.1 KB
/
Copy pathcs2ctl
File metadata and controls
executable file
·422 lines (390 loc) · 20.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
#!/usr/bin/env bash
# Generated by tools/build.py; edit src/ and rebuild.
# SPDX-License-Identifier: GPL-3.0-or-later
set -euo pipefail
CSCTL_DEFAULT_LANGUAGE=zh
readonly GAME="cs2"
default_config_zh() {
cat <<'CSCTL_DEFAULT_CONFIG'
# Bash 配置;仅允许可信管理员编辑。CMD 在 RUN_USER 身份下执行。
RUN_USER='csgo'
SESSION='cs2'
# chown 命令递归修改此目录的用户和组为 RUN_USER:RUN_USER。
CHOWN_DIR='/home/csgo/Steam/steamapps/common/Counter-Strike Global Offensive'
WORKDIR='/home/csgo/Steam/steamapps/common/Counter-Strike Global Offensive/game/bin/linuxsteamrt64'
CMD='export LD_LIBRARY_PATH="$PWD:${LD_LIBRARY_PATH:-}"; exec ./cs2 \
-usercon -dedicated -nomaster -port 27016 -maxplayers_override 20 \
+game_type 0 +game_mode 1 +exec server.cfg +mapgroup mg_bomb \
+map de_dust2 +sv_lan 1'
# 可填写 steamcmd.sh 的绝对路径;这里是单个可执行文件,不含参数。
STEAMCMD='steamcmd'
# CS2 安装根目录,不是 game/bin/linuxsteamrt64。留空使用 SteamCMD 默认目录。
STEAM_INSTALL_DIR='/home/csgo/Steam/steamapps/common/Counter-Strike Global Offensive'
# 留空则在配置文件同级的 cs2ctl-logs 目录保存每次更新和检查日志。
UPDATE_LOG_DIR=''
# 更新成功后,以 RUN_USER 身份在 WORKDIR 中运行 /bin/sh -eu。
# 可直接编写多行 POSIX sh,或调用外部脚本。更新后始终保持停服,需手动 start。
# 可用环境变量:WORKDIR、SESSION、RUN_USER、STEAM_INSTALL_DIR。
POST_UPDATE_SCRIPT=$(cat <<'CSCTL_POST_UPDATE'
# 示例:检查必要内容;路径按你的安装情况修改,再取消注释。
# file="$STEAM_INSTALL_DIR/game/csgo/gameinfo.gi"
# test -f "$file" || { echo "文件不存在: $file" >&2; exit 1; }
# grep -Fq 'Game csgo/addons/metamod' "$file" || {
# echo "检查失败:缺少 Metamod 配置" >&2
# exit 1
# }
# 也可调用:sh /home/csgo/check-after-update.sh
:
CSCTL_POST_UPDATE
)
CSCTL_DEFAULT_CONFIG
}
default_config_en() {
cat <<'CSCTL_DEFAULT_CONFIG'
# Bash configuration; only trusted administrators should edit this file.
RUN_USER='csgo'
SESSION='cs2'
# chown recursively sets this directory's owner and group to RUN_USER:RUN_USER.
CHOWN_DIR='/home/csgo/Steam/steamapps/common/Counter-Strike Global Offensive'
WORKDIR='/home/csgo/Steam/steamapps/common/Counter-Strike Global Offensive/game/bin/linuxsteamrt64'
CMD='export LD_LIBRARY_PATH="$PWD:${LD_LIBRARY_PATH:-}"; exec ./cs2 \
-usercon -dedicated -nomaster -port 27016 -maxplayers_override 20 \
+game_type 0 +game_mode 1 +exec server.cfg +mapgroup mg_bomb \
+map de_dust2 +sv_lan 1'
# One executable name or absolute path to steamcmd.sh, without arguments.
STEAMCMD='steamcmd'
# CS2 installation root, NOT game/bin/linuxsteamrt64. Empty = SteamCMD default.
STEAM_INSTALL_DIR='/home/csgo/Steam/steamapps/common/Counter-Strike Global Offensive'
# Empty = save logs in cs2ctl-logs beside the configuration file.
UPDATE_LOG_DIR=''
# After a successful update, run POSIX sh -eu as RUN_USER in WORKDIR.
# The server stays stopped after updating; use start manually.
# Environment: WORKDIR, SESSION, RUN_USER, STEAM_INSTALL_DIR.
POST_UPDATE_SCRIPT=$(cat <<'CSCTL_POST_UPDATE'
# Example: adjust the path and required text, then uncomment.
# file="$STEAM_INSTALL_DIR/game/csgo/gameinfo.gi"
# test -f "$file" || { echo "File missing: $file" >&2; exit 1; }
# grep -Fq 'Game csgo/addons/metamod' "$file" || {
# echo "Check failed: Metamod configuration missing" >&2
# exit 1
# }
# Or call an external script: sh /home/csgo/check-after-update.sh
:
CSCTL_POST_UPDATE
)
CSCTL_DEFAULT_CONFIG
}
readonly PROGRAM="${GAME}ctl"
UI_LANGUAGE=${CSCTL_LANGUAGE:-$CSCTL_DEFAULT_LANGUAGE}
case $UI_LANGUAGE in
zh|en) ;;
*) printf 'Invalid CSCTL_LANGUAGE: %s (use zh or en)\n' "$UI_LANGUAGE" >&2; exit 1 ;;
esac
CONFIG="/etc/${PROGRAM}.conf"
ACTION=''
VALIDATE_UPDATE=0
EDIT_TEMP=''
message() { if [[ $UI_LANGUAGE == en ]]; then printf '%s' "$2"; else printf '%s' "$1"; fi; }
die() { printf '%s: %s\n' "$PROGRAM" "$(message "$@")" >&2; exit 1; }
info() { printf '[%s] %s\n' "$PROGRAM" "$(message "$@")"; }
default_config() {
if [[ $UI_LANGUAGE == en ]]; then default_config_en; else default_config_zh; fi
}
usage() {
if [[ $UI_LANGUAGE == en ]]; then
cat <<EOF
Usage: $PROGRAM [-c CONFIG] <command>
$PROGRAM [-c CONFIG] -e
Commands:
start Start the tmux session; do nothing if already running
stop Stop the tmux session; succeed if already stopped
restart Stop and start the server
attach Open the console; Ctrl-b d detaches without stopping
status Show session status; exit 3 if not running
chown Recursively set CHOWN_DIR ownership to RUN_USER:RUN_USER
EOF
if [[ $GAME == cs2 ]]; then
cat <<'EOF'
update Stop, update and check; start manually afterward
Add --validate to verify all game files
check Run the post-update script with live and saved logs
EOF
fi
cat <<EOF
Options:
-e, --edit Edit configuration using VISUAL / EDITOR / vi
-c, --config FILE Configuration file (default /etc/${PROGRAM}.conf)
-h, --help Show this help
Language: set CSCTL_LANGUAGE=zh or en to override the installed default.
EOF
return
fi
cat <<EOF
用法: $PROGRAM [-c 配置文件] <命令>
$PROGRAM [-c 配置文件] -e
命令:
start 启动 tmux 会话(已运行时不重复启动)
stop 停止 tmux 会话(已停止时成功返回)
restart 停止后重新启动
attach 进入控制台;Ctrl-b d 分离并保持服务器运行
status 查看会话状态;未运行时退出码为 3
chown 递归修改 CHOWN_DIR 的用户和组为 RUN_USER:RUN_USER
EOF
if [[ $GAME == cs2 ]]; then
cat <<'EOF'
update 先停服,再更新和检查;完成后需手动 start
添加 --validate 可完整校验游戏文件
check 单独运行更新后检查脚本,实时输出并保存日志
EOF
fi
cat <<EOF
选项:
-e, --edit 使用 VISUAL / EDITOR / vi 编辑配置,校验后保存
-c, --config FILE 指定配置文件(默认 /etc/${PROGRAM}.conf)
-h, --help 显示帮助
语言: 设置 CSCTL_LANGUAGE=zh 或 en 可覆盖安装时选择的默认语言。
EOF
}
set_action() {
[[ -z $ACTION ]] || die '只能指定一个命令。' 'Specify only one command.'
ACTION=$1
}
parse_args() {
while (($#)); do
case $1 in
-h|--help) usage; exit 0 ;;
-c|--config)
(($# >= 2)) && [[ -n $2 ]] || die "$1 需要配置文件路径。" "$1 requires a configuration file path."
CONFIG=$2; shift 2 ;;
--config=*) CONFIG=${1#*=}; [[ -n $CONFIG ]] || die '配置路径不能为空。' 'The configuration path cannot be empty.'; shift ;;
-e|--edit) set_action edit; shift ;;
--validate) VALIDATE_UPDATE=1; shift ;;
start|stop|restart|attach|status|chown) set_action "$1"; shift ;;
update|check)
[[ $GAME == cs2 ]] || die "$PROGRAM 不支持 $1。" "$PROGRAM does not support $1."
set_action "$1"; shift ;;
*) usage >&2; die "未知参数: $1" "Unknown argument: $1" ;;
esac
done
[[ -n $ACTION ]] || { usage >&2; exit 2; }
if ((VALIDATE_UPDATE)) && [[ $GAME != cs2 || $ACTION != update ]]; then
die '--validate 仅适用于 cs2ctl update。' '--validate is only supported by cs2ctl update.'
fi
# 转成绝对路径,避免 source 对相对文件名进行 PATH 搜索。
[[ $CONFIG == /* ]] || CONFIG="$PWD/$CONFIG"
}
load_config() {
local file=$1
[[ -f $file && -r $file ]] || die "配置不存在或不可读: $file;请先运行 $PROGRAM -c '$CONFIG' -e。" "Configuration missing or unreadable: $file; run $PROGRAM -c '$CONFIG' -e first."
bash -n "$file" || die "配置语法错误: $file" "Invalid configuration syntax: $file"
RUN_USER='' SESSION='' WORKDIR='' CMD='' CHOWN_DIR=''
STEAMCMD='steamcmd' STEAM_INSTALL_DIR='' UPDATE_LOG_DIR='' POST_UPDATE_SCRIPT=''
# shellcheck disable=SC1090
source "$file" || die "无法加载配置: $file" "Unable to load configuration: $file"
[[ -n $RUN_USER && -n $SESSION && -n $WORKDIR && -n $CMD ]] || die 'RUN_USER、SESSION、WORKDIR、CMD 均不能为空。' 'RUN_USER, SESSION, WORKDIR and CMD must not be empty.'
[[ $RUN_USER != -* && $RUN_USER != *[$'\n\r\t ']* ]] || die 'RUN_USER 必须是有效的系统用户名。' 'RUN_USER must be a valid system username.'
[[ $SESSION =~ ^[a-zA-Z0-9_-]+$ && $SESSION != -* ]] || die 'SESSION 仅支持字母、数字、下划线、短横线,且不能以短横线开头。' 'SESSION may contain letters, digits, underscores and hyphens, and must not start with a hyphen.'
[[ $WORKDIR == /* ]] || die 'WORKDIR 必须是绝对路径。' 'WORKDIR must be an absolute path.'
[[ -z $CHOWN_DIR || $CHOWN_DIR == /* ]] || die 'CHOWN_DIR 必须为空或绝对路径。' 'CHOWN_DIR must be empty or an absolute path.'
bash -n -c "$CMD" || die 'CMD 的 Bash 语法错误。' 'Invalid Bash syntax in CMD.'
if [[ $GAME == cs2 ]]; then
[[ -n $STEAMCMD && $STEAMCMD != -* ]] || die 'STEAMCMD 不能为空或以短横线开头。' 'STEAMCMD must not be empty or start with a hyphen.'
[[ -z $STEAM_INSTALL_DIR || $STEAM_INSTALL_DIR == /* ]] || die 'STEAM_INSTALL_DIR 必须为空或绝对路径。' 'STEAM_INSTALL_DIR must be empty or an absolute path.'
[[ -z $UPDATE_LOG_DIR || $UPDATE_LOG_DIR == /* ]] || die 'UPDATE_LOG_DIR 必须为空或绝对路径。' 'UPDATE_LOG_DIR must be empty or an absolute path.'
sh -n -c "$POST_UPDATE_SCRIPT" || die 'POST_UPDATE_SCRIPT 的 POSIX sh 语法错误。' 'Invalid POSIX sh syntax in POST_UPDATE_SCRIPT.'
fi
}
lock_config() {
command -v flock >/dev/null || die '缺少 flock,请安装 util-linux。' 'flock is missing; install util-linux.'
[[ -d $(dirname -- "$CONFIG") ]] || die "配置目录不存在: $(dirname -- "$CONFIG")" "Configuration directory does not exist: $(dirname -- "$CONFIG")"
# 持锁期间禁止同一配置并发启动、编辑、更新。子进程关闭 FD 9。
exec 9>>"${CONFIG}.lock" || die "无法创建锁文件 ${CONFIG}.lock;请使用 sudo 或可写的配置目录。" "Cannot create ${CONFIG}.lock; use sudo or a writable configuration directory."
flock -n 9 || die '另一个控制操作正在使用此配置,请稍后重试。' 'Another operation is using this configuration; try again later.'
}
edit_config() {
local editor=${VISUAL:-${EDITOR:-vi}}
[[ ! -L $CONFIG ]] || die '请直接编辑配置实际路径,不支持通过符号链接保存配置。' 'Edit the actual configuration path; saving through a symlink is not supported.'
mkdir -p -- "$(dirname -- "$CONFIG")"
lock_config
EDIT_TEMP=$(mktemp "${CONFIG}.edit.XXXXXX")
if [[ -f $CONFIG ]]; then
cat -- "$CONFIG" >"$EDIT_TEMP"
else
default_config >"$EDIT_TEMP"
fi
# 仅 editor 是 shell 命令;文件名通过位置参数传入,支持空格路径。
if ! bash -c "$editor \"\$1\"" -- "$EDIT_TEMP" 9>&-; then
die "编辑器退出失败,原配置未修改。编辑副本保留在: $EDIT_TEMP" "Editor failed; original configuration unchanged. Edited copy retained at: $EDIT_TEMP"
fi
if ! (set -e; load_config "$EDIT_TEMP"); then
die "校验失败,原配置未修改。请修复此副本后复制回配置路径: $EDIT_TEMP" "Validation failed; original configuration unchanged. Fix this copy before copying it to the configuration path: $EDIT_TEMP"
fi
if [[ -f $CONFIG ]] && cmp -s -- "$CONFIG" "$EDIT_TEMP"; then
rm -f -- "$EDIT_TEMP"
info '配置未改变。' 'Configuration unchanged.'
return
fi
chmod 600 "$EDIT_TEMP"
# 同目录 rename 原子替换,保留原配置的权限和所有者。
if [[ -f $CONFIG ]]; then
chmod --reference="$CONFIG" "$EDIT_TEMP"
if ((EUID == 0)); then chown --reference="$CONFIG" "$EDIT_TEMP"; fi
fi
mv -f -- "$EDIT_TEMP" "$CONFIG"
EDIT_TEMP=''
info "已保存配置: $CONFIG" "Configuration saved: $CONFIG"
}
run_as() {
if [[ $(id -un) == "$RUN_USER" ]]; then
"$@" 9>&-
else
# sudo -i 会拼接参数再交给登录 shell,导致 $1/$HOME 等提前展开。
# 直接执行 Bash 登录 shell,原始 argv 通过位置参数传递,保留标准输入。
sudo -H -u "$RUN_USER" -- bash -lc 'cd -- "$HOME" && exec "$@"' \
"$PROGRAM" "$@" 9>&-
fi
}
require_user() {
id "$RUN_USER" >/dev/null 2>&1 || die "系统用户不存在: $RUN_USER" "System user does not exist: $RUN_USER"
}
prepare_user() {
require_user
if [[ $(id -un) != "$RUN_USER" ]]; then
command -v sudo >/dev/null || die '切换运行用户需要 sudo。' 'sudo is required to switch users.'
run_as true || die "无法切换到用户 $RUN_USER。" "Unable to switch to user $RUN_USER."
fi
}
chown_directory() {
local target rc
local -a ownership_command=(chown -R --preserve-root -- "$RUN_USER:$RUN_USER")
require_user
[[ -n $CHOWN_DIR ]] || die '请先通过 -e 在配置中设置 CHOWN_DIR。' 'Set CHOWN_DIR in the configuration using -e first.'
[[ -d $CHOWN_DIR ]] || die "CHOWN_DIR 目录不存在或无法访问: $CHOWN_DIR" "CHOWN_DIR does not exist or is inaccessible: $CHOWN_DIR"
target=$(realpath -e -- "$CHOWN_DIR") || die "无法解析 CHOWN_DIR: $CHOWN_DIR" "Unable to resolve CHOWN_DIR: $CHOWN_DIR"
[[ $target != / ]] || die 'CHOWN_DIR 不能是文件系统根目录。' 'CHOWN_DIR must not be the filesystem root.'
if ((EUID != 0)); then
command -v sudo >/dev/null || die '修改目录所有权需要 sudo 或 root 权限。' 'Changing directory ownership requires sudo or root.'
ownership_command=(sudo "${ownership_command[@]}")
fi
info "正在递归修改 $target 的所有权为 $RUN_USER:$RUN_USER…" "Recursively changing ownership of $target to $RUN_USER:$RUN_USER..."
if "${ownership_command[@]}" "$target" 9>&-; then
info "所有权已更新: $target → $RUN_USER:$RUN_USER" "Ownership updated: $target -> $RUN_USER:$RUN_USER"
else
rc=$?
info "chown 失败(退出码 $rc),部分文件可能已修改,请查看上述错误。" "chown failed (exit $rc); some files may have changed. See the errors above." >&2
return "$rc"
fi
}
require_tmux() {
run_as sh -c 'command -v tmux >/dev/null' || die "用户 $RUN_USER 的 PATH 中找不到 tmux。" "tmux is not in the PATH for user $RUN_USER."
}
is_running() { run_as tmux has-session -t "=$SESSION" 2>/dev/null; }
start_server() {
if is_running; then info "$SESSION 已在运行。" "$SESSION is already running."; return; fi
run_as test -d "$WORKDIR" || die "工作目录不存在或用户 $RUN_USER 无权访问: $WORKDIR" "Working directory missing or inaccessible to $RUN_USER: $WORKDIR"
# 多参数形式让 tmux 直接执行 Bash,避免由用户默认 shell 再解析 CMD。
run_as tmux new-session -d -s "$SESSION" -c "$WORKDIR" bash -c "$CMD"
info "已启动 tmux 会话 $SESSION(用户: $RUN_USER)。" "Started tmux session $SESSION (user: $RUN_USER)."
}
stop_server() {
if ! is_running; then info "$SESSION 已停止。" "$SESSION is already stopped."; return; fi
run_as tmux kill-session -t "=$SESSION"
info "已停止 $SESSION。" "Stopped $SESSION."
}
run_check() {
if [[ -z $POST_UPDATE_SCRIPT ]]; then info '未配置更新后检查脚本,跳过。' 'No post-update script configured; skipping.'; return; fi
info '执行更新后检查脚本…' 'Running the post-update script...'
run_as env WORKDIR="$WORKDIR" SESSION="$SESSION" RUN_USER="$RUN_USER" \
STEAM_INSTALL_DIR="$STEAM_INSTALL_DIR" \
sh -eu -c 'cd "$WORKDIR"; exec sh -eu -c "$1"' "$PROGRAM-check" "$POST_UPDATE_SCRIPT"
}
new_log() {
local kind=$1 logdir
logdir=${UPDATE_LOG_DIR:-"$(dirname -- "$CONFIG")/${PROGRAM}-logs"}
mkdir -p -- "$logdir"
LOG_FILE=$(mktemp "$logdir/${kind}-$(date +%Y%m%d-%H%M%S).XXXXXX.log")
info "日志: $LOG_FILE" "Log: $LOG_FILE"
}
update_steps() {
local rc
local -a steam_args=(+@ShutdownOnFailedCommand 1 +@NoPromptForPassword 1)
if [[ -n $STEAM_INSTALL_DIR ]]; then
steam_args+=(+force_install_dir "$STEAM_INSTALL_DIR")
fi
steam_args+=(+login anonymous +app_update 730)
if ((VALIDATE_UPDATE)); then
steam_args+=(validate)
info "以用户 $RUN_USER 运行 SteamCMD,更新 App 730 并完整校验游戏文件…" "Running SteamCMD as $RUN_USER to update and validate all game files for App 730..."
else
info "以用户 $RUN_USER 运行 SteamCMD,更新 App 730…" "Running SteamCMD as $RUN_USER to update App 730..."
fi
steam_args+=(+quit)
# 在子 shell 中执行并显式保留失败状态,避免后续检查掩盖更新失败。
if run_as "$STEAMCMD" "${steam_args[@]}"; then
:
else
rc=$?; info "SteamCMD 更新失败(退出码 $rc),未运行检查,服务器保持停止。" "SteamCMD update failed (exit $rc); checks skipped and server left stopped."; return "$rc"
fi
# SteamCMD 某些失败会返回 0;必须同时确认 App 730 成功标记。
# 此标记在独立 SteamCMD 日志中检查,见 update_server。
return 0
}
update_server() {
local start_command
local -a result
stop_server
run_as sh -c 'command -v "$1" >/dev/null' sh "$STEAMCMD" || die "用户 $RUN_USER 找不到 SteamCMD: $STEAMCMD" "SteamCMD not found for user $RUN_USER: $STEAMCMD"
new_log update
set +e
update_steps 2>&1 | tee -a "$LOG_FILE"
result=("${PIPESTATUS[@]}")
set -e
((result[0] == 0)) || return "${result[0]}"
((result[1] == 0)) || die '无法完整写入更新日志,停止后续操作。' 'Unable to write the complete update log; aborting further steps.'
if ! grep -Eq "Success! App '?730'? (fully installed|already up to date)\." "$LOG_FILE"; then
die "SteamCMD 未报告 App 730 安装成功;未运行检查,服务器保持停止,请查看 $LOG_FILE" "SteamCMD did not report a successful App 730 installation; checks skipped and server left stopped. See $LOG_FILE"
fi
set +e
(set -e; run_check) 2>&1 | tee -a "$LOG_FILE"
result=("${PIPESTATUS[@]}")
set -e
if ((result[0] != 0)); then
info '更新后检查失败,服务器保持停止。' 'Post-update checks failed; server left stopped.' | tee -a "$LOG_FILE"
return "${result[0]}"
fi
((result[1] == 0)) || die '无法完整写入检查日志,服务器保持停止。' 'Unable to write the complete check log; server left stopped.'
printf -v start_command '%q -c %q start' "$PROGRAM" "$CONFIG"
info "更新和检查完成,服务器保持停止。请手动执行: $start_command" "Update and checks complete; server left stopped. Start manually: $start_command" | tee -a "$LOG_FILE"
}
main() {
umask 077
parse_args "$@"
if [[ $ACTION == edit ]]; then edit_config; return; fi
case $ACTION in
start|stop|restart|update|check|chown) lock_config ;;
esac
load_config "$CONFIG"
if [[ $ACTION == chown ]]; then chown_directory; return; fi
prepare_user
if [[ $ACTION != check ]]; then require_tmux; fi
case $ACTION in
start) start_server ;;
stop) stop_server ;;
# 最后一个会话退出时给 tmux server 留出清理时间,避免立即重连竞争。
restart) stop_server; sleep 1; start_server ;;
attach)
is_running || die "$SESSION 未运行。" "$SESSION is not running."
run_as tmux attach-session -t "=$SESSION" ;;
status)
if is_running; then
# display-message 查找的是窗格;冒号明确指定会话及其活动窗格。
run_as tmux display-message -p -t "=$SESSION:" \
'session=#{session_name} windows=#{session_windows} attached=#{session_attached}'
else info "$SESSION 未运行。" "$SESSION is not running."; return 3; fi ;;
update) update_server ;;
check)
new_log check
run_check 2>&1 | tee -a "$LOG_FILE" ;;
esac
}
main "$@"