# <<*>> -^v- SNOWARE APPLICATION # usage: Automatic operator implementation. # author: S.A. # time: 2026-06-07 # Copyright (c) [2026] [S.A. SNOWARE-SCU] # [MineSentinel] is licensed under Mulan PubL v2. # You can use this software according to the terms and conditions of the Mulan PubL v2. # You may obtain a copy of Mulan PubL v2 at: # http://license.coscl.org.cn/MulanPubL-2.0 # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, # EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. # See the Mulan PubL v2 for more details. # frozen_string_literal: true PERSISTENCE_PERIOD = 30 # Wait for systemctl to take the order. require 'logger' require_relative 'server_status_detector' class MethodExecutor public RST_COUNTS_LIMIT = 10 @@rst_counter = 0 # Initialize the standard log system. @@logger = Logger.new(STDOUT) # This script will be launched as a systemd server. STDOUT.sync = true STDERR.sync = true # Fetch the environmental variable and convert it into log level. @@logger.level = case ENV["LOG_LEVEL"] # @为单个实例(半生不熟的概念,貌似是在不同的空间独立创建),@@为共享同一个 when nil # Default config. Logger::Severity::DEBUG when "debug" Logger::Severity::DEBUG when "info" Logger::Severity::INFO when "warn" Logger::Severity::WARN when "error" Logger::Severity::ERROR else raise ArgumentError end @@logger.progname = "MineSentinel" def self.record(level, module_name, message) case level when "info" @@logger.info("#{module_name}: #{message}") when "warn" @@logger.warn("#{module_name}: #{message}") when "error" @@logger.error("#{module_name}: #{message}") when "debug" @@logger.debug("#{module_name}: #{message}") else raise ArgumentError end end def self.reset_minecraft_server self.record("info", "reset", "Attempt ##{@@rst_counter + 1} to reset Minecraft server.") if @@rst_counter >= RST_COUNTS_LIMIT self.record("error", "reset", "Tried #{RST_COUNTS_LIMIT} times and failed, now I'll idle until server is manually restarted.") while true sleep 10 if ServerStatusDetector::MinecraftServer.self_diag[:type] == true self.record("info", "reset", "Server recovered! Resuming normal monitoring.") @@rst_counter = 0 return end end end @@rst_counter += 1 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 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 end end end end end