From 3f7ebfb52e42529f24d781af8ca9033340c4e72c Mon Sep 17 00:00:00 2001 From: Snowflake Date: Mon, 8 Jun 2026 10:30:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dsystemd=20socket?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E3=80=81=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E3=80=81=E6=8B=86=E5=88=86=E9=85=8D=E7=BD=AE=E5=B8=B8?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.rb | 3 + main.rb | 128 ++++++++++++++++++++------------------ method_executor.rb | 72 +++++++++++---------- server_status_detector.rb | 2 +- 4 files changed, 106 insertions(+), 99 deletions(-) create mode 100644 config.rb diff --git a/config.rb b/config.rb new file mode 100644 index 0000000..113bf46 --- /dev/null +++ b/config.rb @@ -0,0 +1,3 @@ +INITIAL_GRACE = 300 # 初次启动等待 300 秒 +TRY_TIMES = 3 # 尝试次数 +RST_COOLDOWN_DURATION = 200 # 重启冷却期 200 秒 \ No newline at end of file diff --git a/main.rb b/main.rb index b276b0a..c0e1633 100644 --- a/main.rb +++ b/main.rb @@ -12,74 +12,70 @@ # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. # See the Mulan PubL v2 for more details. -# main.rb 顶部添加 -begin - # 原有代码 -rescue => e - # 强制输出到stderr - $stderr.puts "[CRITICAL] Unhandled exception: #{e.message}" - $stderr.puts e.backtrace.join("\n") - exit 1 -end +def main + puts ' ___ __ ___ ___ ___ ' + puts ' |\/| | |\ | |__ /__` |__ |\ | | | |\ | |__ | ' + puts ' | | | | \| |___ .__/ |___ | \| | | | \| |___ |___ ' + puts " MineSentinel dev-rolling version by <*> S.A. [@SNOWARE] and owned by SCU team." + puts " Copyleft (c) 2026 S.A. SNOWARE-SCU. This program is licensed under Mulan PubL v2." + puts " E-mail: SALflake@qq.com | QQ: 758522192 | Website: https://swe-iss.rth1.xyz/softwares/minesentinel." + puts -puts ' ___ __ ___ ___ ___ ' -puts ' |\/| | |\ | |__ /__` |__ |\ | | | |\ | |__ | ' -puts ' | | | | \| |___ .__/ |___ | \| | | | \| |___ |___ ' -puts " MineSentinel dev-rolling version by <*> S.A. [@SNOWARE] and owned by SCU team." -puts " Copyleft (c) 2026 S.A. SNOWARE-SCU. This program is licensed under Mulan PubL v2." -puts " E-mail: SALflake@qq.com | QQ: 758522192 | Website: https://https://swe-iss.rth1.xyz/softwares/minesentinel." -puts + require_relative 'method_executor' # 日志系统在此时初始化 + MethodExecutor.record("debug", "init", "Initialized MethodExecutor, which implements logging system and intervening functions.") + require_relative 'server_status_detector' + require_relative 'systemd_notifier' # 心跳系统在此时初始化 + require_relative 'config' + MethodExecutor.record("debug", "init", "Initialized SystemdNotifier, which implements systemd watchdog feeder.") -require_relative 'method_executor' # 日志系统在此时初始化 -MethodExecutor.record("debug", "init", "Initialized MethodExecutor, which implements logging system and intervening functions.") -require_relative 'server_status_detector' -require_relative 'systemd_notifier' # 心跳系统在此时初始化 -MethodExecutor.record("debug", "init", "Initialized SystemdNotifier, which implements systemd watchdog feeder.") - -# 确认是否具有展开干预操作的权限 如果因场景不同不需要请注释这部分内容 -unless Process.euid == 0 - MethodExecutor.record("error", "init", "Permission denied: root required to intervene Systemd Services.") - raise Errno::EPERM -end - - -MethodExecutor.record("info", "mainloop", "MineSentinel started, entering mainloop.") - -# 初次启动:MineSentinel 先于 minecraft 启动(systemd Before=), -# 等待服务器端口就绪,避免误判为崩溃 -INITIAL_GRACE = 300 # 初次启动等待 300 秒 -MethodExecutor.record("info", "mainloop", - "Initial startup grace period #{INITIAL_GRACE}s — waiting for server to come up...") -grace_deadline = Time.now + INITIAL_GRACE -while Time.now < grace_deadline - sleep 5 - if ServerStatusDetector::MinecraftServer.self_diag[:type] - MethodExecutor.record("info", "mainloop", - "Server port is open during grace period, resuming normal monitoring.") - break + # 确认是否具有展开干预操作的权限 如果因场景不同不需要请注释这部分内容 + unless Process.euid == 0 + MethodExecutor.record("error", "init", "Permission denied: root required to intervene Systemd Services.") + raise Errno::EPERM end + + MethodExecutor.record("info", "mainloop", "MineSentinel started, entering mainloop.") + + # 初次启动:MineSentinel 先于 minecraft 启动(systemd Before=), + # 等待服务器端口就绪,避免误判为崩溃 + MethodExecutor.record("info", "mainloop", + "Initial startup grace period #{INITIAL_GRACE}s — waiting for server to come up...") + grace_deadline = Time.now + INITIAL_GRACE + while Time.now < grace_deadline + sleep 5 + if ServerStatusDetector::MinecraftServer.self_diag[:type] + MethodExecutor.record("info", "mainloop", + "Server port is open during grace period, resuming normal monitoring.") + break + end + end + MethodExecutor.record("info", "mainloop", "Grace period ended.") + main_loop end -MethodExecutor.record("info", "mainloop", "Grace period ended.") # Functional mainloop. -def main - chkrslt = ServerStatusDetector::MinecraftServer.self_diag - if chkrslt[:type] +def main_once + chkrslts = [] + for i in 0..TRY_TIMES + chkrslt = ServerStatusDetector::MinecraftServer.self_diag + chkrslts.append(chkrslt) + end + if chkrslts.all? { |chkrslt_in| chkrslt_in[:type] } MethodExecutor.record("debug", "mainloop", "Server health check passed.") else - MethodExecutor.record("warn", "mainloop", "Server health check FAILED: #{chkrslt[:message]}") - for operation in chkrslt[:operation] - if operation == "Record" - # 消息已在 self_diag 中生成,这里仅记录操作 - MethodExecutor.record("warn", "mainloop", "Action: Record -> Server died!") - elsif operation == "ForceResetServerProcess" + for chkrslt_in in chkrslts + MethodExecutor.record("warn", "mainloop", "Server health check FAILED: #{chkrslt_in[:message]}") + end + MethodExecutor.record("info", "mainloop", "Checked #{TRY_TIMES} time.") + if chkrslts.any? { |chkrslt_in| chkrslt_in[:operation] == "Record" } + MethodExecutor.record("warn", "mainloop", "Action: Record -> Server died!") + if chkrslts.any? { |chkrslt_in| chkrslt_in[:message] == "Server died for unknown reason, and will be restarted." } MethodExecutor.record("info", "mainloop", "Action: ForceResetServerProcess triggered.") MethodExecutor.reset_minecraft_server # 重启后等待服务器启动(MC服务器启动通常需要30秒~2分钟) - cooldown_duration = 200 # 200秒冷却期 cooldown_beg = Time.now - MethodExecutor.record("info", "mainloop", "Cooling down #{cooldown}s for server startup...") - while Time.now < cooldown_beg + cooldown_duration + MethodExecutor.record("info", "mainloop", "Cooling down #{RST_COOLDOWN_DURATION}s for server startup...") + while Time.now < cooldown_beg + RST_COOLDOWN_DURATION sleep 1 SystemdNotifier.notify("WATCHDOG=1") end @@ -89,9 +85,19 @@ def main end end - -while true - sleep 1.5 - SystemdNotifier.notify("WATCHDOG=1") - main +def main_loop + while true + sleep 1.5 + SystemdNotifier.notify("WATCHDOG=1") + main_once + end +end + +begin + main +rescue => e + # 强制输出到stderr + $stderr.puts "[CRITICAL] Unhandled exception: #{e.message}" + $stderr.puts e.backtrace.join("\n") + exit 1 end diff --git a/method_executor.rb b/method_executor.rb index f7becd0..76e9ae5 100644 --- a/method_executor.rb +++ b/method_executor.rb @@ -77,48 +77,46 @@ class MethodExecutor @@rst_counter += 1 - unless Process.euid == 0 - self.record("error", "reset", "Permission denied: root required to manage systemd services.") - raise Errno::EPERM - end + unless Process.euid == 0 + self.record("error", "reset", "Permission denied: root required to manage systemd services.") + raise Errno::EPERM + end - self.record("info", "reset", "Spawning 'systemctl restart minecraft' (PID will be captured)...") - pid = spawn "systemctl restart minecraft" - self.record("debug", "reset", "systemctl restart spawned with PID #{pid}.") - tick_beg = Time.now + self.record("info", "reset", "Spawning 'systemctl restart minecraft' (PID will be captured)...") + pid = spawn "systemctl restart minecraft" + self.record("debug", "reset", "systemctl restart spawned with PID #{pid}.") + tick_beg = Time.now - while true - sleep 0.5 - begin - SystemdNotifier.notify("WATCHDOG=1") - proc_status = Process.waitpid2(pid, Process::WNOHANG) - if proc_status # 子进程已退出,返回 [pid, status] - _, status = proc_status - elapsed = (Time.now - tick_beg).round(2) - if status.success? - self.record("info", "reset", "'systemctl restart' succeeded in #{elapsed}s.") - @@rst_counter = 0 # 成功时重置计数器 - break - else - self.record("error", "reset", "'systemctl restart' failed with exitcode #{status.exitstatus} after #{elapsed}s.") - break - end - else # 子进程仍在运行 (WNOHANG 返回 nil) - if Time.now - tick_beg > PERSISTENCE_PERIOD then - self.record("warn", "reset", - "'systemctl restart' timed out after #{PERSISTENCE_PERIOD}s. Force-killing PID #{pid}...") - system "kill -9 #{pid}" - self.record("warn", "reset", "Running 'systemctl kill minecraft' (last resort)...") - system "systemctl kill minecraft" # 没招了,毁灭吧 - sleep 0.5 - self.record("info", "reset", "Running 'systemctl start minecraft' (rebirth)...") - system "systemctl start minecraft" # 重生 - self.record("info", "reset", "Force-reset sequence completed.") - break - end + while true + sleep 0.5 + SystemdNotifier.notify("WATCHDOG=1") + proc_status = Process.waitpid2(pid, Process::WNOHANG) + if proc_status # 子进程已退出,返回 [pid, status] + _, status = proc_status + elapsed = (Time.now - tick_beg).round(2) + if status.success? + self.record("info", "reset", "'systemctl restart' succeeded in #{elapsed}s.") + @@rst_counter = 0 # 成功时重置计数器 + break + else + self.record("error", "reset", "'systemctl restart' failed with exitcode #{status.exitstatus} after #{elapsed}s.") + break end + else # 子进程仍在运行 (WNOHANG 返回 nil) + if Time.now - tick_beg > PERSISTENCE_PERIOD then + self.record("warn", "reset", + "'systemctl restart' timed out after #{PERSISTENCE_PERIOD}s. Force-killing PID #{pid}...") + system "kill -9 #{pid}" + self.record("warn", "reset", "Running 'systemctl kill minecraft' (last resort)...") + system "systemctl kill minecraft" # 没招了,毁灭吧 + sleep 0.5 + self.record("info", "reset", "Running 'systemctl start minecraft' (rebirth)...") + system "systemctl start minecraft" # 重生 + self.record("info", "reset", "Force-reset sequence completed.") + break end end + end end diff --git a/server_status_detector.rb b/server_status_detector.rb index fc2e2c7..559ce77 100644 --- a/server_status_detector.rb +++ b/server_status_detector.rb @@ -55,7 +55,7 @@ module ServerStatusDetector class MinecraftServer < TcpServer # 继承为TcpServer编写的属性和方法 - def self.self_diag() + def self_diag res = test_port('127.0.0.1', 25565)