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 10:30:38 +08:00
|
|
|
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
|
2026-06-08 08:39:05 +08:00
|
|
|
|
2026-06-08 10:30:38 +08:00
|
|
|
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.")
|
2026-06-08 08:39:05 +08:00
|
|
|
|
2026-06-08 10:30:38 +08:00
|
|
|
# 确认是否具有展开干预操作的权限 如果因场景不同不需要请注释这部分内容
|
|
|
|
|
unless Process.euid == 0
|
|
|
|
|
MethodExecutor.record("error", "init", "Permission denied: root required to intervene Systemd Services.")
|
|
|
|
|
raise Errno::EPERM
|
|
|
|
|
end
|
2026-06-07 11:05:26 +08:00
|
|
|
|
2026-06-08 10:30:38 +08:00
|
|
|
MethodExecutor.record("info", "mainloop", "MineSentinel started, entering mainloop.")
|
2026-06-07 11:05:26 +08:00
|
|
|
|
2026-06-08 10:30:38 +08:00
|
|
|
# 初次启动: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
|
2026-06-07 19:35:17 +08:00
|
|
|
end
|
2026-06-08 10:30:38 +08:00
|
|
|
MethodExecutor.record("info", "mainloop", "Grace period ended.")
|
|
|
|
|
main_loop
|
2026-06-07 19:35:17 +08:00
|
|
|
end
|
|
|
|
|
|
2026-06-07 11:05:26 +08:00
|
|
|
# Functional mainloop.
|
2026-06-08 10:30:38 +08:00
|
|
|
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] }
|
2026-06-07 11:05:26 +08:00
|
|
|
MethodExecutor.record("debug", "mainloop", "Server health check passed.")
|
|
|
|
|
else
|
2026-06-08 10:30:38 +08:00
|
|
|
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." }
|
2026-06-07 11:05:26 +08:00
|
|
|
MethodExecutor.record("info", "mainloop", "Action: ForceResetServerProcess triggered.")
|
|
|
|
|
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 10:30:38 +08:00
|
|
|
MethodExecutor.record("info", "mainloop", "Cooling down #{RST_COOLDOWN_DURATION}s for server startup...")
|
|
|
|
|
while Time.now < cooldown_beg + RST_COOLDOWN_DURATION
|
2026-06-08 07:30:21 +08:00
|
|
|
sleep 1
|
|
|
|
|
SystemdNotifier.notify("WATCHDOG=1")
|
|
|
|
|
end
|
2026-06-07 13:10:15 +08:00
|
|
|
MethodExecutor.record("info", "mainloop", "Cooldown finished, resuming monitoring.")
|
2026-06-07 11:05:26 +08:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2026-06-08 10:30:38 +08:00
|
|
|
def main_loop
|
|
|
|
|
while true
|
|
|
|
|
sleep 1.5
|
|
|
|
|
SystemdNotifier.notify("WATCHDOG=1")
|
|
|
|
|
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 10:30:38 +08:00
|
|
|
rescue => e
|
|
|
|
|
# 强制输出到stderr
|
|
|
|
|
$stderr.puts "[CRITICAL] Unhandled exception: #{e.message}"
|
|
|
|
|
$stderr.puts e.backtrace.join("\n")
|
|
|
|
|
exit 1
|
2026-06-07 11:05:26 +08:00
|
|
|
end
|