Compare commits

...

5 Commits

Author SHA1 Message Date
sa 4927bdf019 Update version code 2026-06-08 20:50:54 +08:00
sa 5d2e13db0c fix: updates.rb 处理 Gitee raw URL 302 重定向 2026-06-08 20:31:50 +08:00
sa d494093850 fix: 全面修复5个逻辑问题 2026-06-08 20:21:47 +08:00
sa 5668d6589f Oh my watchdog. 2026-06-08 20:10:58 +08:00
sa 41ef365e25 Adding a heartbeat, avoiding dying cause of one failure. 2026-06-08 19:43:17 +08:00
6 changed files with 47 additions and 28 deletions
+3 -1
View File
@@ -3,4 +3,6 @@ b43527c
8cd942b
83acd30
b2baee0
13c4c19
13c4c19
5668d65
d494093
+16 -12
View File
@@ -59,6 +59,7 @@ def main
grace_deadline = Time.now + INITIAL_GRACE
while Time.now < grace_deadline
sleep 5
SystemdNotifier.notify('WATCHDOG=1')
next unless ServerStatusDetector::MinecraftServer.self_diag[:type]
MethodExecutor.record('info', 'mainloop',
@@ -77,29 +78,32 @@ def main_once
(0...TRY_TIMES).each do
chkrslt = ServerStatusDetector::MinecraftServer.self_diag
chkrslts.append(chkrslt)
sleep 0.5 unless chkrslt[:type] # 只在失败时等待后再重试
end
if chkrslts.all? { |chkrslt_in| chkrslt_in[:type] }
MethodExecutor.record('debug', 'mainloop', 'Server health check passed.')
MethodExecutor.reset_rst_counter
else
chkrslts.each do |result|
MethodExecutor.record('warn', 'mainloop', "Server health check FAILED: #{result[:message]}")
end
MethodExecutor.record('info', 'mainloop', "Checked #{TRY_TIMES} time.")
if chkrslts.any? { |chkrslt_in| chkrslt_in[:operation].include?('Record') }
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.' }
if chkrslts.any? { |chkrslt_in| (chkrslt_in[:operation] || []).include?('ForceResetServerProcess') }
MethodExecutor.record('info', 'mainloop', 'Action: ForceResetServerProcess triggered.')
MethodExecutor.reset_minecraft_server
# 重启后等待服务器启动(MC服务器启动通常需要30秒~2分钟)
cooldown_beg = Time.now
MethodExecutor.record('info', 'mainloop', "Cooling down #{RST_COOLDOWN_DURATION}s for server startup...")
# rubocop:disable Metrics/BlockNesting
while Time.now < cooldown_beg + RST_COOLDOWN_DURATION
sleep 1
SystemdNotifier.notify('WATCHDOG=1')
if MethodExecutor.reset_minecraft_server
# 重启后等待服务器启动(MC服务器启动通常需要30秒~2分钟)
cooldown_beg = Time.now
MethodExecutor.record('info', 'mainloop', "Cooling down #{RST_COOLDOWN_DURATION}s for server startup...")
# rubocop:disable Metrics/BlockNesting
while Time.now < cooldown_beg + RST_COOLDOWN_DURATION
sleep 1
SystemdNotifier.notify('WATCHDOG=1')
end
# rubocop:enable Metrics/BlockNesting
MethodExecutor.record('info', 'mainloop', 'Cooldown finished, resuming monitoring.')
end
# rubocop:enable Metrics/BlockNesting
MethodExecutor.record('info', 'mainloop', 'Cooldown finished, resuming monitoring.')
end
end
end
+10 -4
View File
@@ -66,13 +66,13 @@ class MethodExecutor
# rubocop:enable Metrics/MethodLength
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# @return [void]
# @return [Boolean] true if a restart was attempted, false if recovered from idle
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.")
"Tried #{RST_COUNTS_LIMIT} times and failed, now I'll idle until server is manually recovered.")
loop do
sleep 10
SystemdNotifier.notify('WATCHDOG=1') if defined?(SystemdNotifier)
@@ -80,7 +80,7 @@ class MethodExecutor
record('info', 'reset', 'Server recovered! Resuming normal monitoring.')
@@rst_counter = 0
return
return false
end
end
@@ -106,7 +106,6 @@ class MethodExecutor
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.")
@@ -128,6 +127,13 @@ class MethodExecutor
end
# rubocop:enable Metrics/BlockLength
end
true
end
# Resets the restart counter when server health check passes.
# @return [void]
def self.reset_rst_counter
@@rst_counter = 0
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# rubocop:enable Style/ClassVars
+1 -1
View File
@@ -24,7 +24,7 @@ module ServerStatusDetector
# @param port [Integer]
# @param timeout [Integer]
# @return [Hash{Symbol=>Integer, Symbol=>Symbol, Symbol=>Float,nil}]
def self.test_port(host = '127.0.0.1', port = 25_565, timeout = 10)
def self.test_port(host = '127.0.0.1', port = 25_565, timeout = 2)
result = {
port: port,
status: :unknown,
+1 -1
View File
@@ -17,6 +17,7 @@
# Sends keep-alive notifications (including WATCHDOG=1) to systemd
# via the NOTIFY_SOCKET datagram socket, enabling service supervision.
class SystemdNotifier
require 'socket'
require_relative 'method_executor'
@@ -52,7 +53,6 @@ class SystemdNotifier
MethodExecutor.record('debug', 'heartbeat', "Heartbeat sent: #{message}")
rescue StandardError => e
MethodExecutor.record('error', 'heartbeat', "Failed to send heartbeat: #{e}")
@enable_heartbeat = false
end
end
end
+16 -9
View File
@@ -18,11 +18,11 @@ require 'net/http'
require 'uri'
# @type const RELEASE_SERIAL_CODE: String
RELEASE_SERIAL_CODE = '13c4c19'
RELEASE_SERIAL_CODE = 'd494093'
# @type const CHANNEL: String
CHANNEL = 'dev'
# @type const VERSION_LIST_URL: String
VERSION_LIST_URL = "https://gitee.com/api/v5/repos/SCU_team/mine-sentinel/contents/APP_VERSIONS_LIST?ref=#{CHANNEL}"
VERSION_LIST_URL = "https://gitee.com/SCU_team/mine-sentinel/raw/#{CHANNEL}/APP_VERSIONS_LIST"
# @type const RECURSIVE_RETRY_DEPTH: Integer
RECURSIVE_RETRY_DEPTH = 3
@@ -32,14 +32,21 @@ class UpdateManager
def self.fetch
MethodExecutor.record('debug', 'updates', "Fetching version list from #{VERSION_LIST_URL}")
uri = URI(VERSION_LIST_URL)
response = Net::HTTP.get_response(uri)
case response.code.to_i
when 200
MethodExecutor.record('debug', 'updates', 'Version list fetched successfully.')
response.body
else
raise "Unexpected HTTP response code: #{response.code}"
5.times do # 跟随重定向(Gitee raw URL → raw.giteeusercontent.com
response = Net::HTTP.get_response(uri)
case response.code.to_i
when 200
MethodExecutor.record('debug', 'updates', 'Version list fetched successfully.')
return response.body
when 301, 302, 307, 308
uri = URI(response['location'])
MethodExecutor.record('debug', 'updates', "Following redirect to #{uri}")
next
else
raise "Unexpected HTTP response code: #{response.code}"
end
end
raise 'Too many redirects'
rescue StandardError => e
MethodExecutor.record('error', 'updates', "Failed to fetch version list: #{e}")
nil