2026-06-07 11:05:26 +08:00
|
|
|
# <<*>> -^v- SNOWARE APPLICATION
|
|
|
|
|
# usage: MINESENTINEL mainloop.
|
|
|
|
|
# author: S.A.
|
|
|
|
|
# time: 2026-06-07
|
2026-06-07 19:35:17 +08:00
|
|
|
# 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.
|
2026-06-07 11:05:26 +08:00
|
|
|
|
2026-06-08 11:47:00 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
|
|
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
2026-06-08 10:30:38 +08:00
|
|
|
def main
|
|
|
|
|
puts ' ___ __ ___ ___ ___ '
|
|
|
|
|
puts ' |\/| | |\ | |__ /__` |__ |\ | | | |\ | |__ | '
|
|
|
|
|
puts ' | | | | \| |___ .__/ |___ | \| | | | \| |___ |___ '
|
2026-06-08 11:47:00 +08:00
|
|
|
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.'
|
2026-06-08 10:30:38 +08:00
|
|
|
puts
|
2026-06-08 08:39:05 +08:00
|
|
|
|
2026-06-08 10:30:38 +08:00
|
|
|
require_relative 'method_executor' # 日志系统在此时初始化
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('debug', 'init',
|
|
|
|
|
'Initialized MethodExecutor, which implements logging system and intervening functions.')
|
2026-06-08 10:30:38 +08:00
|
|
|
require_relative 'server_status_detector'
|
|
|
|
|
require_relative 'systemd_notifier' # 心跳系统在此时初始化
|
|
|
|
|
require_relative 'config'
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('debug', 'init', 'Initialized SystemdNotifier, which implements systemd watchdog feeder.')
|
2026-06-08 08:39:05 +08:00
|
|
|
|
2026-06-08 10:30:38 +08:00
|
|
|
# 确认是否具有展开干预操作的权限 如果因场景不同不需要请注释这部分内容
|
2026-06-08 11:47:00 +08:00
|
|
|
unless Process.euid.zero?
|
|
|
|
|
MethodExecutor.record('error', 'init', 'Permission denied: root required to intervene Systemd Services.')
|
2026-06-08 10:30:38 +08:00
|
|
|
raise Errno::EPERM
|
|
|
|
|
end
|
2026-06-07 11:05:26 +08:00
|
|
|
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('info', 'mainloop', 'MineSentinel started, entering mainloop.')
|
2026-06-07 11:05:26 +08:00
|
|
|
|
2026-06-08 11:35:51 +08:00
|
|
|
require_relative 'updates'
|
|
|
|
|
|
|
|
|
|
begin
|
|
|
|
|
upd_result = UpdateManager.check
|
|
|
|
|
if upd_result[:status] == :NewerAvailable
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('info', 'updates',
|
|
|
|
|
"Update #{upd_result[:version]} available, please check the git repo for details.")
|
2026-06-08 11:35:51 +08:00
|
|
|
end
|
2026-06-08 11:47:00 +08:00
|
|
|
rescue StandardError => e
|
|
|
|
|
MethodExecutor.record('error', 'updates', e.to_s)
|
2026-06-08 11:35:51 +08:00
|
|
|
end
|
|
|
|
|
|
2026-06-08 10:30:38 +08:00
|
|
|
# 初次启动:MineSentinel 先于 minecraft 启动(systemd Before=),
|
|
|
|
|
# 等待服务器端口就绪,避免误判为崩溃
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('info', 'mainloop',
|
|
|
|
|
"Initial startup grace period #{INITIAL_GRACE}s — waiting for server to come up...")
|
2026-06-08 10:30:38 +08:00
|
|
|
grace_deadline = Time.now + INITIAL_GRACE
|
|
|
|
|
while Time.now < grace_deadline
|
|
|
|
|
sleep 5
|
2026-06-08 11:47:00 +08:00
|
|
|
next unless ServerStatusDetector::MinecraftServer.self_diag[:type]
|
|
|
|
|
|
|
|
|
|
MethodExecutor.record('info', 'mainloop',
|
|
|
|
|
'Server port is open during grace period, resuming normal monitoring.')
|
|
|
|
|
break
|
2026-06-07 19:35:17 +08:00
|
|
|
end
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('info', 'mainloop', 'Grace period ended.')
|
2026-06-08 10:30:38 +08:00
|
|
|
main_loop
|
2026-06-07 19:35:17 +08:00
|
|
|
end
|
|
|
|
|
|
2026-06-08 11:47:00 +08:00
|
|
|
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
2026-06-07 11:05:26 +08:00
|
|
|
# Functional mainloop.
|
2026-06-08 11:47:00 +08:00
|
|
|
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
2026-06-08 10:30:38 +08:00
|
|
|
def main_once
|
|
|
|
|
chkrslts = []
|
2026-06-08 11:35:51 +08:00
|
|
|
(0..TRY_TIMES).each do
|
2026-06-08 10:30:38 +08:00
|
|
|
chkrslt = ServerStatusDetector::MinecraftServer.self_diag
|
|
|
|
|
chkrslts.append(chkrslt)
|
|
|
|
|
end
|
|
|
|
|
if chkrslts.all? { |chkrslt_in| chkrslt_in[:type] }
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('debug', 'mainloop', 'Server health check passed.')
|
2026-06-07 11:05:26 +08:00
|
|
|
else
|
2026-06-08 11:35:51 +08:00
|
|
|
chkrslts.each do |result|
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('warn', 'mainloop', "Server health check FAILED: #{result[:message]}")
|
2026-06-08 10:30:38 +08:00
|
|
|
end
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('info', 'mainloop', "Checked #{TRY_TIMES} time.")
|
|
|
|
|
if chkrslts.any? { |chkrslt_in| chkrslt_in[:operation].include?('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.')
|
2026-06-07 11:05:26 +08:00
|
|
|
MethodExecutor.reset_minecraft_server
|
2026-06-07 13:10:15 +08:00
|
|
|
# 重启后等待服务器启动(MC服务器启动通常需要30秒~2分钟)
|
2026-06-08 07:30:21 +08:00
|
|
|
cooldown_beg = Time.now
|
2026-06-08 11:47:00 +08:00
|
|
|
MethodExecutor.record('info', 'mainloop', "Cooling down #{RST_COOLDOWN_DURATION}s for server startup...")
|
|
|
|
|
# rubocop:disable Metrics/BlockNesting
|
2026-06-08 10:30:38 +08:00
|
|
|
while Time.now < cooldown_beg + RST_COOLDOWN_DURATION
|
2026-06-08 07:30:21 +08:00
|
|
|
sleep 1
|
2026-06-08 11:47:00 +08:00
|
|
|
SystemdNotifier.notify('WATCHDOG=1')
|
2026-06-08 07:30:21 +08:00
|
|
|
end
|
2026-06-08 11:47:00 +08:00
|
|
|
# rubocop:enable Metrics/BlockNesting
|
|
|
|
|
MethodExecutor.record('info', 'mainloop', 'Cooldown finished, resuming monitoring.')
|
2026-06-07 11:05:26 +08:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2026-06-08 11:47:00 +08:00
|
|
|
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
2026-06-08 10:30:38 +08:00
|
|
|
def main_loop
|
2026-06-08 11:47:00 +08:00
|
|
|
loop do
|
2026-06-08 10:37:12 +08:00
|
|
|
sleep CHECK_INTERVAL
|
2026-06-08 11:47:00 +08:00
|
|
|
SystemdNotifier.notify('WATCHDOG=1')
|
2026-06-08 10:30:38 +08:00
|
|
|
main_once
|
|
|
|
|
end
|
|
|
|
|
end
|
2026-06-07 11:05:26 +08:00
|
|
|
|
2026-06-08 10:30:38 +08:00
|
|
|
begin
|
2026-06-07 11:05:26 +08:00
|
|
|
main
|
2026-06-08 11:47:00 +08:00
|
|
|
rescue StandardError => e
|
2026-06-08 10:30:38 +08:00
|
|
|
# 强制输出到stderr
|
2026-06-08 11:47:00 +08:00
|
|
|
warn "[CRITICAL] Unhandled exception: #{e.message}"
|
|
|
|
|
warn e.backtrace&.join("\n")
|
2026-06-08 10:30:38 +08:00
|
|
|
exit 1
|
2026-06-07 11:05:26 +08:00
|
|
|
end
|