Skip to content

Instantly share code, notes, and snippets.

@ihainan
Created January 28, 2026 01:29
Show Gist options
  • Select an option

  • Save ihainan/1db1315a84bf3cc9dbe28523568a5954 to your computer and use it in GitHub Desktop.

Select an option

Save ihainan/1db1315a84bf3cc9dbe28523568a5954 to your computer and use it in GitHub Desktop.
#!/bin/bash
# Steam 和游戏进程暂停管理脚本
# 基于 Nyrna 的 SIGSTOP/SIGCONT 机制
# 用法: ./process-pause-manager.sh [check|pause|unpause|toggle]
# 切换到脚本所在目录
cd "$(dirname "$0")"
# 设置日志文件
LOG_FILE="process-pause-manager.log"
# 日志函数
log_message() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $message" | tee -a "$LOG_FILE"
}
# 静默日志(仅写入文件)
log_silent() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $message" >> "$LOG_FILE"
}
# 记录脚本开始执行
if [ "${1:-check}" != "check" ]; then
log_message "脚本开始执行,参数: ${1:-check}"
else
log_silent "脚本开始执行,参数: check"
fi
# 获取容器 ID
get_container_id() {
docker-compose ps -q | head -1
}
# 检查容器是否运行
check_container_running() {
local container_id=$(get_container_id)
if [ -z "$container_id" ]; then
return 1
fi
local status=$(docker inspect --format='{{.State.Running}}' "$container_id" 2>/dev/null)
if [ "$status" = "true" ]; then
return 0
else
return 1
fi
}
# 一次性获取所有子进程(批量方式,大幅提升性能)
# 参数: $1=容器ID, $2=父进程PID, $3=排序方式("deep_first"从深到浅,"shallow_first"从浅到深)
get_all_children_batch() {
local container_id="$1"
local parent_pid="$2"
local order="${3:-deep_first}"
docker exec "$container_id" sh -c "
ps -eo pid,ppid | awk -v root=$parent_pid -v order=$order '
BEGIN {
todo[root] = 1
depth[root] = 0
}
{
parent[\$1] = \$2
children[\$2] = children[\$2] \" \" \$1
}
END {
# BFS 遍历,记录每个节点的深度
while (length(todo) > 0) {
for (p in todo) {
current = p
current_depth = depth[current]
delete todo[p]
break
}
n = split(children[current], kids)
for (i = 1; i <= n; i++) {
if (kids[i] != \"\") {
depth[kids[i]] = current_depth + 1
max_depth = (current_depth + 1 > max_depth) ? current_depth + 1 : max_depth
todo[kids[i]] = 1
all_pids[kids[i]] = 1
}
}
}
# 按深度排序输出
if (order == \"deep_first\") {
# 从深到浅(用于暂停:先暂停子进程)
for (d = max_depth; d >= 0; d--) {
for (pid in all_pids) {
if (depth[pid] == d) print pid
}
}
} else {
# 从浅到深(用于恢复:先恢复父进程)
for (d = 0; d <= max_depth; d++) {
for (pid in all_pids) {
if (depth[pid] == d) print pid
}
}
}
}
'
" 2>/dev/null || true
}
# 获取进程状态(通过 ps 命令)
# 返回: T=已暂停, R/S/I=运行中, 其他=未知
get_process_status() {
local container_id="$1"
local pid="$2"
local status=$(docker exec "$container_id" sh -c "ps -o state= -p $pid 2>/dev/null | head -c1" 2>/dev/null || echo "")
echo "$status"
}
# 暂停单个进程(快速版本,不验证)
suspend_pid() {
local container_id="$1"
local pid="$2"
local process_name="$3"
# 发送 SIGSTOP 信号(不验证,加快速度)
docker exec "$container_id" sh -c "kill -STOP $pid" 2>/dev/null
return $?
}
# 恢复单个进程(快速版本,不验证)
resume_pid() {
local container_id="$1"
local pid="$2"
local process_name="$3"
# 发送 SIGCONT 信号(不验证,加快速度)
docker exec "$container_id" sh -c "kill -CONT $pid" 2>/dev/null
return $?
}
# 批量暂停进程树(先暂停子进程,再暂停父进程)
suspend_process_tree() {
local container_id="$1"
local pid="$2"
local process_name="$3"
# 1. 获取所有子进程(按深度排序:从深到浅)
local children=$(get_all_children_batch "$container_id" "$pid" "deep_first")
# 2. 批量暂停所有子进程(一次 kill 命令)
if [ -n "$children" ]; then
# 将换行符转换为空格,确保 kill 命令正确执行
local children_list=$(echo "$children" | tr '\n' ' ')
docker exec "$container_id" sh -c "kill -STOP $children_list" 2>/dev/null
fi
# 3. 最后暂停父进程
suspend_pid "$container_id" "$pid" "$process_name"
}
# 批量恢复进程树(先恢复父进程,再恢复子进程)
resume_process_tree() {
local container_id="$1"
local pid="$2"
local process_name="$3"
# 1. 先恢复父进程
resume_pid "$container_id" "$pid" "$process_name"
# 2. 获取所有子进程(按深度排序:从浅到深)
local children=$(get_all_children_batch "$container_id" "$pid" "shallow_first")
# 3. 批量恢复所有子进程(一次 kill 命令)
if [ -n "$children" ]; then
# 将换行符转换为空格,确保 kill 命令正确执行
local children_list=$(echo "$children" | tr '\n' ' ')
docker exec "$container_id" sh -c "kill -CONT $children_list" 2>/dev/null
fi
}
# 查找 Steam 和游戏相关进程
find_target_processes() {
local container_id="$1"
# 查找 Steam 相关进程(排除 steamwebhelper - 太多子进程)
# 主要暂停:
# 1. steam (主进程)
# 2. reaper (Steam 的子进程管理器)
# 3. 游戏进程(通过 SteamLaunch 标识)
# 一次性查找所有目标进程,减少 docker exec 调用
local all_processes=$(docker exec "$container_id" sh -c '
# 查找 Steam 主进程
steam_pid=$(pgrep -x steam 2>/dev/null | head -1)
[ -n "$steam_pid" ] && echo "steam:$steam_pid"
# 查找 reaper 进程
pgrep -x reaper 2>/dev/null | sed "s/^/reaper:/"
# 查找游戏进程(通过 SteamLaunch 或 applaunch 标识)
# 一次性获取所有信息(PID 和进程名)
ps aux | grep -E "(SteamLaunch|applaunch)" | grep -v grep | awk "{
pid = \$2
cmd = \"ps -o comm= -p \" pid \" 2>/dev/null || echo game\"
cmd | getline comm
close(cmd)
print comm \":\" pid
}"
' 2>/dev/null || true)
echo "$all_processes"
}
# 检查是否有进程被暂停
check_suspended_status() {
if ! check_container_running; then
echo "No"
return 1
fi
local container_id=$(get_container_id)
local target_processes=$(find_target_processes "$container_id")
if [ -z "$target_processes" ]; then
echo "No"
return 1
fi
# 检查第一个进程的状态
local first_process=$(echo "$target_processes" | awk '{print $1}')
local process_name=$(echo "$first_process" | cut -d: -f1)
local pid=$(echo "$first_process" | cut -d: -f2)
local status=$(get_process_status "$container_id" "$pid")
if [ "$status" = "T" ]; then
echo "Yes"
return 0
else
echo "No"
return 1
fi
}
# 暂停所有目标进程
pause_processes() {
log_message "========================================"
log_message "开始暂停游戏和 Steam 进程"
log_message "========================================"
if ! check_container_running; then
log_message "✗ 容器未运行"
return 1
fi
local container_id=$(get_container_id)
log_message "目标容器: $container_id"
# 查找所有目标进程
local target_processes=$(find_target_processes "$container_id")
if [ -z "$target_processes" ]; then
log_message "⚠ 未找到 Steam 或游戏进程"
log_message "========================================"
return 1
fi
# 统计进程数量
local total_count=$(echo "$target_processes" | wc -w)
log_message "找到 $total_count 个目标进程,开始暂停..."
# 暂停每个进程树(批量处理,减少日志)
local success_count=0
for process_entry in $target_processes; do
local process_name=$(echo "$process_entry" | cut -d: -f1)
local pid=$(echo "$process_entry" | cut -d: -f2)
if suspend_process_tree "$container_id" "$pid" "$process_name"; then
success_count=$((success_count + 1))
fi
done
log_message "========================================"
log_message "暂停完成: $success_count/$total_count 个进程树"
log_message "========================================"
if [ $success_count -gt 0 ]; then
return 0
else
return 1
fi
}
# 恢复所有目标进程
unpause_processes() {
log_message "========================================"
log_message "开始恢复游戏和 Steam 进程"
log_message "========================================"
if ! check_container_running; then
log_message "✗ 容器未运行"
return 1
fi
local container_id=$(get_container_id)
log_message "目标容器: $container_id"
# 查找所有目标进程
local target_processes=$(find_target_processes "$container_id")
if [ -z "$target_processes" ]; then
log_message "⚠ 未找到 Steam 或游戏进程"
log_message "========================================"
return 1
fi
# 统计进程数量
local total_count=$(echo "$target_processes" | wc -w)
log_message "找到 $total_count 个目标进程,开始恢复..."
# 恢复每个进程树(批量处理,减少日志)
local success_count=0
for process_entry in $target_processes; do
local process_name=$(echo "$process_entry" | cut -d: -f1)
local pid=$(echo "$process_entry" | cut -d: -f2)
if resume_process_tree "$container_id" "$pid" "$process_name"; then
success_count=$((success_count + 1))
fi
done
log_message "========================================"
log_message "恢复完成: $success_count/$total_count 个进程树"
log_message "========================================"
if [ $success_count -gt 0 ]; then
return 0
else
return 1
fi
}
# 切换进程状态
toggle_processes() {
local status=$(check_suspended_status)
if [ "$status" = "Yes" ]; then
unpause_processes
else
pause_processes
fi
}
# 主逻辑
case "${1:-check}" in
"check")
result=$(check_suspended_status)
echo "$result"
log_silent "检查结果: $result"
;;
"pause")
status=$(check_suspended_status)
if [ "$status" = "Yes" ]; then
log_message "进程已经被暂停"
else
pause_processes
fi
;;
"unpause")
status=$(check_suspended_status)
if [ "$status" = "Yes" ]; then
unpause_processes
else
log_message "进程没有被暂停"
fi
;;
"toggle")
toggle_processes
;;
*)
echo "用法: $0 [check|pause|unpause|toggle]"
echo " check - 检查进程是否被暂停 (默认操作)"
echo " pause - 暂停 Steam 和游戏进程"
echo " unpause - 恢复 Steam 和游戏进程"
echo " toggle - 切换暂停状态"
log_message "显示帮助信息"
exit 1
;;
esac
# 记录脚本执行完成
if [ "${1:-check}" != "check" ]; then
log_message "脚本执行完成"
else
log_silent "脚本执行完成"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment