Created
January 28, 2026 01:29
-
-
Save ihainan/1db1315a84bf3cc9dbe28523568a5954 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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