ets2-server/server/server.sh
ASUS 4bf1a0522d v1.0.3
- 新增stop后断后还占用
- 新增debug功能
- 制作时间更新
- debug版本
2026-05-07 04:41:09 +08:00

756 lines
19 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/sh
# 2026年5月7日04点25分
# 项目地址: https://git.a-hxin.cn/ahxin/ets2-server/rss/branch/master/server/server.sh
# ==============================
# ETS2 Dedicated Server 管理脚本
# ==============================
# 服务器目录
SERVER_HOME="/home/steam/ets2_sv/bin/linux_x64"
# 官方启动脚本
SERVER_LAUNCH="$SERVER_HOME/server_launch.sh"
# Steam 运行库目录
STEAM_PATH="/home/steam/ets2_sv/linux64"
# SteamCMD 目录
STEAMCMD_HOME="/home/steam/steamcmd"
# ETS2 服务端安装目录
ETS2_INSTALL_DIR="/home/steam/ets2_sv"
# ETS2 Dedicated Server AppID
ETS2_APP_ID="1948160"
# 欧卡文档目录
# 实际读取目录:
# /home/steam/ets2_doc/Euro Truck Simulator 2
export XDG_DATA_HOME="/home/steam/ets2_doc"
# 设置 LD_LIBRARY_PATH
export LD_LIBRARY_PATH="$STEAM_PATH:$SERVER_HOME:${LD_LIBRARY_PATH:-}"
# 启动参数
SERVER_OPTIONS="-nosingle -server server_packages.sii -server_cfg server_config.sii"
# 实际配置目录
ETS2_DOC_HOME="$XDG_DATA_HOME/Euro Truck Simulator 2"
# 日志根目录
LOG_ROOT="$SERVER_HOME/logs"
# PID 文件
PID_FILE="$LOG_ROOT/server.pid"
# 后台启动器 PID 文件
RUNNER_PID_FILE="$LOG_ROOT/runner.pid"
# 进程组 ID 文件
PGID_FILE="$LOG_ROOT/server.pgid"
# 最新日志软链接
LATEST_LOG_LINK="$LOG_ROOT/latest.log"
# 当前日志路径记录
CURRENT_LOG_PATH_FILE="$LOG_ROOT/current_log.path"
# 日志保留天数
LOG_KEEP_DAYS=15
# 当前运行日志文件
SERVER_LOG=""
show_info() {
echo "[INFO] $1"
}
show_ok() {
echo "[OK] $1"
}
show_warn() {
echo "[WARN] $1"
}
show_error() {
echo "[ERROR] $1"
}
prepare_dirs() {
mkdir -p "$LOG_ROOT"
mkdir -p "$ETS2_DOC_HOME"
}
create_log_file() {
LOG_DAY=$(date +"%Y-%m-%d")
LOG_TIME=$(date +"%Y%m%d_%H%M%S")
LOG_DIR="$LOG_ROOT/$LOG_DAY"
mkdir -p "$LOG_DIR"
SERVER_LOG="$LOG_DIR/server_$LOG_TIME.log"
touch "$SERVER_LOG"
ln -sfn "$SERVER_LOG" "$LATEST_LOG_LINK"
echo "$SERVER_LOG" > "$CURRENT_LOG_PATH_FILE"
show_info "本次日志文件: $SERVER_LOG"
}
cleanup_old_logs() {
if [ -d "$LOG_ROOT" ]; then
find "$LOG_ROOT" -mindepth 1 -maxdepth 1 -type d -mtime +"$LOG_KEEP_DAYS" -exec rm -rf {} \; 2>/dev/null
fi
}
check_required_files() {
echo "========== ETS2 配置文件检查 =========="
echo "当前用户: $(whoami)"
echo "SERVER_HOME: $SERVER_HOME"
echo "SERVER_LAUNCH: $SERVER_LAUNCH"
echo "STEAM_PATH: $STEAM_PATH"
echo "XDG_DATA_HOME: $XDG_DATA_HOME"
echo "ETS2_DOC_HOME: $ETS2_DOC_HOME"
echo "LD_LIBRARY_PATH: $LD_LIBRARY_PATH"
echo "SERVER_OPTIONS: $SERVER_OPTIONS"
echo ""
if [ -x "$SERVER_LAUNCH" ]; then
echo "[OK] server_launch.sh 存在并可执行"
else
echo "[ERROR] server_launch.sh 不存在或不可执行"
fi
if [ -f "$ETS2_DOC_HOME/server_config.sii" ]; then
echo "[OK] server_config.sii 存在"
else
echo "[WARN] 缺少 server_config.sii"
fi
if [ -f "$ETS2_DOC_HOME/server_packages.sii" ]; then
echo "[OK] server_packages.sii 存在"
else
echo "[ERROR] 缺少 server_packages.sii"
fi
if [ -f "$ETS2_DOC_HOME/server_packages.dat" ]; then
echo "[OK] server_packages.dat 存在"
else
echo "[ERROR] 缺少 server_packages.dat"
fi
echo ""
echo "当前配置目录文件:"
ls -lah "$ETS2_DOC_HOME" 2>/dev/null
echo "======================================="
}
get_server_pid() {
pgrep -f "eurotrucks2_server" | head -n 1
}
get_launch_pid() {
pgrep -f "server_launch.sh" | head -n 1
}
get_config_port() {
KEY="$1"
CONFIG_FILE="$ETS2_DOC_HOME/server_config.sii"
if [ ! -f "$CONFIG_FILE" ]; then
return
fi
grep -E "^[[:space:]]*$KEY[[:space:]]*:" "$CONFIG_FILE" | \
head -n 1 | \
sed -E 's/.*:[[:space:]]*([0-9]+).*/\1/'
}
get_connection_port() {
PORT=$(get_config_port "connection_dedicated_port")
if [ -z "$PORT" ]; then
PORT="27015"
fi
echo "$PORT"
}
get_query_port() {
PORT=$(get_config_port "query_dedicated_port")
if [ -z "$PORT" ]; then
PORT="27016"
fi
echo "$PORT"
}
port_in_use() {
PORT="$1"
if [ -z "$PORT" ]; then
return 1
fi
ss -H -lntup 2>/dev/null | awk -v port=":$PORT" '
$5 ~ port"$" {
found=1
}
END {
exit !found
}
'
}
show_port_owner() {
PORT="$1"
ss -lntup 2>/dev/null | grep -E ":$PORT[[:space:]]|:$PORT$" || true
}
get_port_owner_pids() {
PORT="$1"
ss -H -lntup 2>/dev/null | awk -v port=":$PORT" '
$0 ~ port {
line=$0
while (match(line, /pid=[0-9]+/)) {
pid=substr(line, RSTART + 4, RLENGTH - 4)
print pid
line=substr(line, RSTART + RLENGTH)
}
}
' | sort -u
}
get_ets2_pid_from_ports() {
CONN_PORT=$(get_connection_port)
QUERY_PORT=$(get_query_port)
for PORT in "$CONN_PORT" "$QUERY_PORT"; do
for P in $(get_port_owner_pids "$PORT"); do
CMD=$(ps -p "$P" -o command= 2>/dev/null)
case "$CMD" in
*eurotrucks2_server*)
echo "$P"
return 0
;;
esac
done
done
return 1
}
check_ports_available() {
CONN_PORT=$(get_connection_port)
QUERY_PORT=$(get_query_port)
echo "========== ETS2 端口检查 =========="
echo "connection_dedicated_port: $CONN_PORT"
echo "query_dedicated_port: $QUERY_PORT"
echo ""
HAS_ERROR=0
if port_in_use "$CONN_PORT"; then
show_error "端口 $CONN_PORT 已被占用"
show_port_owner "$CONN_PORT"
HAS_ERROR=1
else
show_ok "端口 $CONN_PORT 可用"
fi
if port_in_use "$QUERY_PORT"; then
show_error "端口 $QUERY_PORT 已被占用"
show_port_owner "$QUERY_PORT"
HAS_ERROR=1
else
show_ok "端口 $QUERY_PORT 可用"
fi
echo "=================================="
if [ "$HAS_ERROR" -ne 0 ]; then
echo ""
show_error "端口被占用,请先执行:"
echo "$0 stop"
echo ""
echo "如果 stop 后仍占用,执行:"
echo "$0 kill"
return 1
fi
return 0
}
show_ports_status() {
CONN_PORT=$(get_connection_port)
QUERY_PORT=$(get_query_port)
echo "========== ETS2 端口状态 =========="
echo "connection_dedicated_port: $CONN_PORT"
show_port_owner "$CONN_PORT"
echo ""
echo "query_dedicated_port: $QUERY_PORT"
show_port_owner "$QUERY_PORT"
echo "=================================="
}
pgid_has_ets2() {
TARGET_PGID="$1"
if [ -z "$TARGET_PGID" ]; then
return 1
fi
ps -eo pgid=,cmd= | awk -v pgid="$TARGET_PGID" '
$1 == pgid && ($0 ~ /eurotrucks2_server/ || $0 ~ /server_launch.sh/ || $0 ~ /script .*server_launch.sh/) {
found=1
}
END {
exit !found
}
'
}
kill_ets2_port_owners() {
CONN_PORT=$(get_connection_port)
QUERY_PORT=$(get_query_port)
for PORT in "$CONN_PORT" "$QUERY_PORT"; do
for P in $(get_port_owner_pids "$PORT"); do
CMD=$(ps -p "$P" -o command= 2>/dev/null)
case "$CMD" in
*eurotrucks2_server*|*server_launch.sh*)
show_warn "端口 $PORT 仍被 ETS2 进程占用,强制清理 PID: $P"
kill -TERM "$P" 2>/dev/null || true
sleep 1
kill -KILL "$P" 2>/dev/null || true
;;
*)
show_warn "端口 $PORT 被非 ETS2 进程占用PID: $P"
echo "$CMD"
;;
esac
done
done
}
wait_ports_release() {
CONN_PORT=$(get_connection_port)
QUERY_PORT=$(get_query_port)
COUNT=0
while [ "$COUNT" -lt 10 ]; do
if ! port_in_use "$CONN_PORT" && ! port_in_use "$QUERY_PORT"; then
show_ok "端口 $CONN_PORT / $QUERY_PORT 已释放。"
return 0
fi
sleep 1
COUNT=$((COUNT + 1))
done
show_warn "端口仍未完全释放,尝试按端口兜底清理..."
kill_ets2_port_owners
sleep 2
if ! port_in_use "$CONN_PORT" && ! port_in_use "$QUERY_PORT"; then
show_ok "端口 $CONN_PORT / $QUERY_PORT 已释放。"
return 0
fi
show_error "端口仍被占用:"
show_port_owner "$CONN_PORT"
show_port_owner "$QUERY_PORT"
return 1
}
force_cleanup_server() {
prepare_dirs
show_warn "正在清理残留 ETS2 进程..."
RUNNER_PID=""
PGID=""
if [ -f "$RUNNER_PID_FILE" ]; then
RUNNER_PID=$(cat "$RUNNER_PID_FILE")
fi
if [ -f "$PGID_FILE" ]; then
PGID=$(cat "$PGID_FILE")
fi
if [ -n "$PGID" ]; then
if pgid_has_ets2 "$PGID"; then
show_warn "正在停止 ETS2 进程组 PGID: $PGID"
kill -TERM "-$PGID" 2>/dev/null || true
else
show_warn "PGID $PGID 不存在或不属于 ETS2跳过进程组终止"
fi
fi
if [ -n "$RUNNER_PID" ]; then
show_warn "正在停止启动器 PID: $RUNNER_PID"
kill -TERM "$RUNNER_PID" 2>/dev/null || true
fi
pkill -TERM -f "eurotrucks2_server" 2>/dev/null || true
pkill -TERM -f "server_launch.sh" 2>/dev/null || true
pkill -TERM -f "script .*server_launch.sh" 2>/dev/null || true
sleep 3
if [ -n "$PGID" ]; then
if pgid_has_ets2 "$PGID"; then
kill -KILL "-$PGID" 2>/dev/null || true
fi
fi
if [ -n "$RUNNER_PID" ]; then
kill -KILL "$RUNNER_PID" 2>/dev/null || true
fi
pkill -KILL -f "eurotrucks2_server" 2>/dev/null || true
pkill -KILL -f "server_launch.sh" 2>/dev/null || true
pkill -KILL -f "script .*server_launch.sh" 2>/dev/null || true
kill_ets2_port_owners
rm -f "$PID_FILE"
rm -f "$RUNNER_PID_FILE"
rm -f "$PGID_FILE"
wait_ports_release || true
show_ok "残留进程清理完成。"
}
run_server_realtime() {
cd "$SERVER_HOME" || exit 1
if command -v script >/dev/null 2>&1; then
script -qefc "$SERVER_LAUNCH $SERVER_OPTIONS" /dev/null
elif command -v stdbuf >/dev/null 2>&1; then
stdbuf -oL -eL "$SERVER_LAUNCH" $SERVER_OPTIONS
else
"$SERVER_LAUNCH" $SERVER_OPTIONS
fi
}
case "$1" in
start)
show_info "正在启动 ETS2 服务器..."
prepare_dirs
cleanup_old_logs
SERVER_PID=$(get_server_pid)
if [ -z "$SERVER_PID" ]; then
SERVER_PID=$(get_ets2_pid_from_ports)
fi
if [ -n "$SERVER_PID" ]; then
show_warn "ETS2 服务器似乎已经在运行PID: $SERVER_PID"
echo "$SERVER_PID" > "$PID_FILE"
if [ -f "$CURRENT_LOG_PATH_FILE" ]; then
show_info "本次日志: $(cat "$CURRENT_LOG_PATH_FILE")"
fi
exit 0
fi
check_ports_available || exit 1
create_log_file
cd "$SERVER_HOME" || exit 1
setsid sh -c '
SERVER_HOME="$1"
SERVER_LAUNCH="$2"
SERVER_OPTIONS="$3"
SERVER_LOG="$4"
cd "$SERVER_HOME" || exit 1
if command -v script >/dev/null 2>&1; then
script -qefc "$SERVER_LAUNCH $SERVER_OPTIONS" /dev/null
elif command -v stdbuf >/dev/null 2>&1; then
stdbuf -oL -eL "$SERVER_LAUNCH" $SERVER_OPTIONS
else
"$SERVER_LAUNCH" $SERVER_OPTIONS
fi 2>&1 | awk '"'"'{print strftime("%Y-%m-%d %H:%M:%S"), "-", $0; fflush();}'"'"' >> "$SERVER_LOG"
' sh "$SERVER_HOME" "$SERVER_LAUNCH" "$SERVER_OPTIONS" "$SERVER_LOG" >/dev/null 2>&1 &
RUNNER_PID=$!
echo "$RUNNER_PID" > "$RUNNER_PID_FILE"
echo "$RUNNER_PID" > "$PGID_FILE"
show_info "启动器 PID: $RUNNER_PID"
show_info "进程组 PGID: $RUNNER_PID"
show_info "等待 ETS2 服务端启动,最多等待 60 秒..."
WAIT_COUNT=0
SERVER_PID=""
LAUNCH_PID=""
while [ "$WAIT_COUNT" -lt 60 ]; do
SERVER_PID=$(get_server_pid)
if [ -z "$SERVER_PID" ]; then
SERVER_PID=$(get_ets2_pid_from_ports)
fi
LAUNCH_PID=$(get_launch_pid)
if [ -n "$SERVER_PID" ]; then
echo "$SERVER_PID" > "$PID_FILE"
show_ok "ETS2 服务器已启动PID: $SERVER_PID"
show_info "最新日志: $LATEST_LOG_LINK"
show_info "本次日志: $SERVER_LOG"
exit 0
fi
if [ -n "$LAUNCH_PID" ]; then
show_info "启动脚本仍在运行PID: $LAUNCH_PID,继续等待..."
fi
if grep -qiE "Failed to init|Server was terminated|couldn't find an open port|Server packages file not found|SteamAPI_Init.*failed|\*\*\* ERROR \*\*\*" "$SERVER_LOG" 2>/dev/null; then
show_error "检测到启动错误,准备清理残留进程..."
tail -n 100 "$SERVER_LOG"
force_cleanup_server
exit 1
fi
WAIT_COUNT=$((WAIT_COUNT + 1))
sleep 1
done
SERVER_PID=$(get_server_pid)
if [ -z "$SERVER_PID" ]; then
SERVER_PID=$(get_ets2_pid_from_ports)
fi
if [ -n "$SERVER_PID" ]; then
echo "$SERVER_PID" > "$PID_FILE"
show_ok "ETS2 服务器已启动PID: $SERVER_PID"
show_info "最新日志: $LATEST_LOG_LINK"
show_info "本次日志: $SERVER_LOG"
exit 0
fi
show_error "等待 60 秒后仍未检测到 ETS2 主进程,准备清理残留进程..."
tail -n 120 "$SERVER_LOG"
force_cleanup_server
exit 1
;;
debug)
show_info "正在以前台调试模式启动 ETS2 服务器..."
show_warn "当前不会后台运行,按 Ctrl+C 可停止服务器。"
prepare_dirs
cleanup_old_logs
create_log_file
check_required_files
OLD_PID=$(get_server_pid)
if [ -z "$OLD_PID" ]; then
OLD_PID=$(get_ets2_pid_from_ports)
fi
if [ -n "$OLD_PID" ]; then
show_error "检测到已有 ETS2 服务端进程正在运行PID: $OLD_PID"
echo ""
echo "请先执行:"
echo "$0 stop"
echo ""
echo "如果仍然无法释放端口,执行:"
echo "$0 kill"
exit 1
fi
check_ports_available || exit 1
trap '
echo ""
echo "[WARN] 收到中断信号,正在退出 debug 模式..."
force_cleanup_server
echo "[OK] debug 残留进程已清理。"
exit 130
' INT TERM HUP
echo ""
echo "========== 开始前台调试 =========="
echo "执行命令:"
echo "$SERVER_LAUNCH $SERVER_OPTIONS"
echo "本次日志:"
echo "$SERVER_LOG"
echo "=================================="
echo ""
run_server_realtime 2>&1 | \
awk '{print strftime("%Y-%m-%d %H:%M:%S"), "-", $0; fflush();}' | \
tee -a "$SERVER_LOG"
trap - INT TERM HUP
show_warn "debug 模式已退出,正在清理可能残留的 ETS2 进程..."
force_cleanup_server
;;
stop)
show_info "正在停止 ETS2 服务器..."
force_cleanup_server
;;
kill)
force_cleanup_server
;;
restart)
show_info "正在重启 ETS2 服务器..."
"$0" stop
sleep 3
"$0" start
;;
update)
show_info "正在更新 ETS2 专用服务器..."
SERVER_PID=$(get_server_pid)
if [ -n "$SERVER_PID" ]; then
show_warn "检测到服务器正在运行,先停止服务器..."
"$0" stop
sleep 3
fi
mkdir -p "$STEAMCMD_HOME" "$ETS2_INSTALL_DIR"
cd "$STEAMCMD_HOME" || exit 1
show_info "检查 SteamCMD..."
curl -sqL "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" | tar zxvf -
chmod +x "$STEAMCMD_HOME/steamcmd.sh"
show_info "开始更新 ETS2 Dedicated Server..."
"$STEAMCMD_HOME/steamcmd.sh" \
+force_install_dir "$ETS2_INSTALL_DIR" \
+login anonymous \
+app_update "$ETS2_APP_ID" validate \
+quit
show_ok "ETS2 专用服务器更新完成。"
;;
status)
prepare_dirs
SERVER_PID=$(get_server_pid)
if [ -z "$SERVER_PID" ]; then
SERVER_PID=$(get_ets2_pid_from_ports)
fi
LAUNCH_PID=$(get_launch_pid)
if [ -n "$SERVER_PID" ]; then
show_ok "ETS2 服务器正在运行PID: $SERVER_PID"
if [ -n "$LAUNCH_PID" ]; then
show_info "启动脚本进程 PID: $LAUNCH_PID"
fi
if [ -f "$CURRENT_LOG_PATH_FILE" ]; then
show_info "本次日志: $(cat "$CURRENT_LOG_PATH_FILE")"
fi
show_info "最新日志: $LATEST_LOG_LINK"
echo ""
show_ports_status
else
show_warn "ETS2 服务器未运行。"
rm -f "$PID_FILE"
fi
;;
log)
prepare_dirs
if [ -L "$LATEST_LOG_LINK" ] || [ -f "$LATEST_LOG_LINK" ]; then
tail -n 100 -F "$LATEST_LOG_LINK"
else
show_warn "暂无最新日志文件。"
show_info "请先执行:$0 start 或 $0 debug"
fi
;;
listlog)
prepare_dirs
echo "最近日志文件:"
find "$LOG_ROOT" -type f -name "*.log" 2>/dev/null | sort | tail -n 30
;;
cleanlog)
prepare_dirs
cleanup_old_logs
show_ok "已清理超过 $LOG_KEEP_DAYS 天的日志目录。"
;;
check)
prepare_dirs
check_required_files
echo ""
check_ports_available || true
;;
ports)
prepare_dirs
check_ports_available || true
;;
portstatus)
prepare_dirs
show_ports_status
;;
*)
echo "ETS2 服务器管理命令"
echo "用法: ets2_sv {start|stop|restart|status|update|debug|log|listlog|cleanlog|check|ports|portstatus|kill}"
echo ""
echo " start - 后台启动 ETS2 服务器"
echo " stop - 停止 ETS2 服务器,并释放端口"
echo " restart - 重启 ETS2 服务器"
echo " status - 查看 ETS2 服务器状态"
echo " update - 更新 ETS2 专用服务器"
echo " debug - 前台调试启动,不后台运行,实时输出"
echo " log - 实时查看最新日志"
echo " listlog - 查看最近日志文件"
echo " cleanlog - 清理旧日志"
echo " check - 检查配置文件和端口"
echo " ports - 检查端口是否可用"
echo " portstatus - 查看端口占用详情"
echo " kill - 强制清理残留 ETS2 进程"
exit 1
;;
esac