# <<*>> -^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' # rubocop:disable Style/ClassVars class MethodExecutor 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.fetch('LOG_LEVEL', nil) # @为单个实例(半生不熟的概念,貌似是在不同的空间独立创建),@@为共享同一个 when nil, 'debug' # Default config. 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' # rubocop:disable Metrics/MethodLength # @param level [String] "info" | "warn" | "error" | "debug" # @param module_name [String] # @param message [String] # @return [void] 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 # rubocop:enable Metrics/MethodLength # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity # @return [void] def self.reset_minecraft_server record('info', 'reset', "Attempt ##{@@rst_counter + 1} to reset Minecraft server.") if @@rst_counter >= RST_COUNTS_LIMIT record('error', 'reset', "Tried #{RST_COUNTS_LIMIT} times and failed, now I'll idle until server is manually restarted.") loop do sleep 10 SystemdNotifier.notify('WATCHDOG=1') if defined?(SystemdNotifier) next unless ServerStatusDetector::MinecraftServer.self_diag[:type] == true record('info', 'reset', 'Server recovered! Resuming normal monitoring.') @@rst_counter = 0 return end end @@rst_counter += 1 unless Process.euid.zero? record('error', 'reset', 'Permission denied: root required to manage systemd services.') raise Errno::EPERM end record('info', 'reset', "Spawning 'systemctl restart minecraft' (PID will be captured)...") pid = spawn 'systemctl restart minecraft' record('debug', 'reset', "systemctl restart spawned with PID #{pid}.") tick_beg = Time.now # rubocop:disable Metrics/BlockLength loop do 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? record('info', 'reset', "'systemctl restart' succeeded in #{elapsed}s.") @@rst_counter = 0 # 成功时重置计数器 else record('error', 'reset', "'systemctl restart' failed with exitcode #{status.exitstatus} after #{elapsed}s.") end break else # 子进程仍在运行 (WNOHANG 返回 nil) if Time.now - tick_beg > PERSISTENCE_PERIOD record('warn', 'reset', "'systemctl restart' timed out after #{PERSISTENCE_PERIOD}s. Force-killing PID #{pid}...") system "kill -9 #{pid}" record('warn', 'reset', "Running 'systemctl kill minecraft' (last resort)...") system 'systemctl kill minecraft' # 没招了,毁灭吧 sleep 0.5 record('info', 'reset', "Running 'systemctl start minecraft' (rebirth)...") system 'systemctl start minecraft' # 重生 record('info', 'reset', 'Force-reset sequence completed.') break end end # rubocop:enable Metrics/BlockLength end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity # rubocop:enable Style/ClassVars end