release: 发布版本 60176fa,RuboCop 零告警,网页新增版本号展示

This commit is contained in:
2026-06-08 11:47:00 +08:00
parent 60176fa316
commit 8cd942be2d
9 changed files with 171 additions and 140 deletions
+6
View File
@@ -0,0 +1,6 @@
AllCops:
NewCops: enable
Layout/EndOfLine:
Enabled: false
Style/Documentation:
Enabled: false
+1
View File
@@ -1 +1,2 @@
b43527c b43527c
60176fa
+3
View File
@@ -453,6 +453,9 @@
<p class="subtitle">Minecraft 服务器智能守护进程</p> <p class="subtitle">Minecraft 服务器智能守护进程</p>
<p class="tagline">TCP 检测 · 自动重启 · 防循环冷却</p> <p class="tagline">TCP 检测 · 自动重启 · 防循环冷却</p>
<p class="author-badge"><strong>SCU 团队</strong> 拥有 · 编写者 <strong>S.A. [@SNOWARE]</strong></p> <p class="author-badge"><strong>SCU 团队</strong> 拥有 · 编写者 <strong>S.A. [@SNOWARE]</strong></p>
<p style="margin-top:12px;font-size:0.88rem;opacity:0.7;color:#fcd34d;">
当前版本 <code style="background:rgba(255,255,255,0.15);color:#fff;padding:2px 8px;border-radius:4px;">60176fa</code> · 频道 <code style="background:rgba(255,255,255,0.15);color:#fff;padding:2px 8px;border-radius:4px;">master</code>
</p>
</header> </header>
<!-- Features --> <!-- Features -->
+1 -1
View File
@@ -15,7 +15,7 @@
# frozen_string_literal: true # frozen_string_literal: true
# @type const INITIAL_GRACE: Integer # @type const INITIAL_GRACE: Integer
INITIAL_GRACE = 300 # 初次启动等待 300 秒 INITIAL_GRACE = 300 # 初次启动等待 300 秒
# @type const TRY_TIMES: Integer # @type const TRY_TIMES: Integer
TRY_TIMES = 3 # 尝试次数 TRY_TIMES = 3 # 尝试次数
# @type const RST_COOLDOWN_DURATION: Integer # @type const RST_COOLDOWN_DURATION: Integer
+44 -34
View File
@@ -12,59 +12,66 @@
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PubL v2 for more details. # See the Mulan PubL v2 for more details.
# frozen_string_literal: true
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def main def main
puts ' ___ __ ___ ___ ___ ' puts ' ___ __ ___ ___ ___ '
puts ' |\/| | |\ | |__ /__` |__ |\ | | | |\ | |__ | ' puts ' |\/| | |\ | |__ /__` |__ |\ | | | |\ | |__ | '
puts ' | | | | \| |___ .__/ |___ | \| | | | \| |___ |___ ' puts ' | | | | \| |___ .__/ |___ | \| | | | \| |___ |___ '
puts " MineSentinel dev-rolling version by <*> S.A. [@SNOWARE] and owned by SCU team." 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 ' 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 ' E-mail: SALflake@qq.com | QQ: 758522192 | Website: https://swe-iss.rth1.xyz/softwares/minesentinel.'
puts puts
require_relative 'method_executor' # 日志系统在此时初始化 require_relative 'method_executor' # 日志系统在此时初始化
MethodExecutor.record("debug", "init", "Initialized MethodExecutor, which implements logging system and intervening functions.") MethodExecutor.record('debug', 'init',
'Initialized MethodExecutor, which implements logging system and intervening functions.')
require_relative 'server_status_detector' require_relative 'server_status_detector'
require_relative 'systemd_notifier' # 心跳系统在此时初始化 require_relative 'systemd_notifier' # 心跳系统在此时初始化
require_relative 'config' require_relative 'config'
MethodExecutor.record("debug", "init", "Initialized SystemdNotifier, which implements systemd watchdog feeder.") MethodExecutor.record('debug', 'init', 'Initialized SystemdNotifier, which implements systemd watchdog feeder.')
# 确认是否具有展开干预操作的权限 如果因场景不同不需要请注释这部分内容 # 确认是否具有展开干预操作的权限 如果因场景不同不需要请注释这部分内容
unless Process.euid == 0 unless Process.euid.zero?
MethodExecutor.record("error", "init", "Permission denied: root required to intervene Systemd Services.") MethodExecutor.record('error', 'init', 'Permission denied: root required to intervene Systemd Services.')
raise Errno::EPERM raise Errno::EPERM
end end
MethodExecutor.record("info", "mainloop", "MineSentinel started, entering mainloop.") MethodExecutor.record('info', 'mainloop', 'MineSentinel started, entering mainloop.')
require_relative 'updates' require_relative 'updates'
begin begin
upd_result = UpdateManager.check upd_result = UpdateManager.check
if upd_result[:status] == :NewerAvailable if upd_result[:status] == :NewerAvailable
MethodExecutor.record("info", "updates", "Update #{upd_result[:version]} available, please check the git repo for details.") MethodExecutor.record('info', 'updates',
"Update #{upd_result[:version]} available, please check the git repo for details.")
end end
rescue => check_error rescue StandardError => e
MethodExecutor.record("error", "updates", "#{check_error.to_s}") MethodExecutor.record('error', 'updates', e.to_s)
end end
# 初次启动:MineSentinel 先于 minecraft 启动(systemd Before=), # 初次启动:MineSentinel 先于 minecraft 启动(systemd Before=),
# 等待服务器端口就绪,避免误判为崩溃 # 等待服务器端口就绪,避免误判为崩溃
MethodExecutor.record("info", "mainloop", MethodExecutor.record('info', 'mainloop',
"Initial startup grace period #{INITIAL_GRACE}s — waiting for server to come up...") "Initial startup grace period #{INITIAL_GRACE}s — waiting for server to come up...")
grace_deadline = Time.now + INITIAL_GRACE grace_deadline = Time.now + INITIAL_GRACE
while Time.now < grace_deadline while Time.now < grace_deadline
sleep 5 sleep 5
if ServerStatusDetector::MinecraftServer.self_diag[:type] next unless ServerStatusDetector::MinecraftServer.self_diag[:type]
MethodExecutor.record("info", "mainloop",
"Server port is open during grace period, resuming normal monitoring.") MethodExecutor.record('info', 'mainloop',
break 'Server port is open during grace period, resuming normal monitoring.')
end break
end end
MethodExecutor.record("info", "mainloop", "Grace period ended.") MethodExecutor.record('info', 'mainloop', 'Grace period ended.')
main_loop main_loop
end end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
# Functional mainloop. # Functional mainloop.
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def main_once def main_once
chkrslts = [] chkrslts = []
(0..TRY_TIMES).each do (0..TRY_TIMES).each do
@@ -72,43 +79,46 @@ def main_once
chkrslts.append(chkrslt) chkrslts.append(chkrslt)
end end
if chkrslts.all? { |chkrslt_in| chkrslt_in[:type] } if chkrslts.all? { |chkrslt_in| chkrslt_in[:type] }
MethodExecutor.record("debug", "mainloop", "Server health check passed.") MethodExecutor.record('debug', 'mainloop', 'Server health check passed.')
else else
chkrslts.each do |result| chkrslts.each do |result|
MethodExecutor.record("warn", "mainloop", "Server health check FAILED: #{result[:message]}") MethodExecutor.record('warn', 'mainloop', "Server health check FAILED: #{result[:message]}")
end end
MethodExecutor.record("info", "mainloop", "Checked #{TRY_TIMES} time.") 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!") 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[:message] == 'Server died for unknown reason, and will be restarted.' }
MethodExecutor.record("info", "mainloop", "Action: ForceResetServerProcess triggered.") MethodExecutor.record('info', 'mainloop', 'Action: ForceResetServerProcess triggered.')
MethodExecutor.reset_minecraft_server MethodExecutor.reset_minecraft_server
# 重启后等待服务器启动(MC服务器启动通常需要30秒~2分钟) # 重启后等待服务器启动(MC服务器启动通常需要30秒~2分钟)
cooldown_beg = Time.now cooldown_beg = Time.now
MethodExecutor.record("info", "mainloop", "Cooling down #{RST_COOLDOWN_DURATION}s for server startup...") 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 while Time.now < cooldown_beg + RST_COOLDOWN_DURATION
sleep 1 sleep 1
SystemdNotifier.notify("WATCHDOG=1") SystemdNotifier.notify('WATCHDOG=1')
end end
MethodExecutor.record("info", "mainloop", "Cooldown finished, resuming monitoring.") # rubocop:enable Metrics/BlockNesting
MethodExecutor.record('info', 'mainloop', 'Cooldown finished, resuming monitoring.')
end end
end end
end end
end end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
def main_loop def main_loop
while true loop do
sleep CHECK_INTERVAL sleep CHECK_INTERVAL
SystemdNotifier.notify("WATCHDOG=1") SystemdNotifier.notify('WATCHDOG=1')
main_once main_once
end end
end end
begin begin
main main
rescue => e rescue StandardError => e
# 强制输出到stderr # 强制输出到stderr
$stderr.puts "[CRITICAL] Unhandled exception: #{e.message}" warn "[CRITICAL] Unhandled exception: #{e.message}"
$stderr.puts e.backtrace&.join("\n") warn e.backtrace&.join("\n")
exit 1 exit 1
end end
+59 -55
View File
@@ -18,30 +18,31 @@ PERSISTENCE_PERIOD = 30 # Wait for systemctl to take the order.
require 'logger' require 'logger'
require_relative 'server_status_detector' require_relative 'server_status_detector'
# rubocop:disable Style/ClassVars
class MethodExecutor class MethodExecutor
public
RST_COUNTS_LIMIT = 10 RST_COUNTS_LIMIT = 10
@@rst_counter = 0 @@rst_counter = 0
# Initialize the standard log system. # Initialize the standard log system.
@@logger = Logger.new(STDOUT) # This script will be launched as a systemd server. @@logger = Logger.new($stdout) # This script will be launched as a systemd server.
STDOUT.sync = true $stdout.sync = true
STDERR.sync = true $stderr.sync = true
# Fetch the environmental variable and convert it into log level. # Fetch the environmental variable and convert it into log level.
@@logger.level = case ENV["LOG_LEVEL"] # @为单个实例(半生不熟的概念,貌似是在不同的空间独立创建),@@为共享同一个 @@logger.level = case ENV.fetch('LOG_LEVEL', nil) # @为单个实例(半生不熟的概念,貌似是在不同的空间独立创建),@@为共享同一个
when nil # Default config. when nil, 'debug' # Default config.
Logger::Severity::DEBUG Logger::Severity::DEBUG
when "debug" when 'info'
Logger::Severity::DEBUG Logger::Severity::INFO
when "info" when 'warn'
Logger::Severity::INFO Logger::Severity::WARN
when "warn" when 'error'
Logger::Severity::WARN Logger::Severity::ERROR
when "error" else
Logger::Severity::ERROR raise ArgumentError
else
raise ArgumentError
end end
@@logger.progname = "MineSentinel" @@logger.progname = 'MineSentinel'
# rubocop:disable Metrics/MethodLength
# @param level [String] "info" | "warn" | "error" | "debug" # @param level [String] "info" | "warn" | "error" | "debug"
# @param module_name [String] # @param module_name [String]
@@ -49,82 +50,85 @@ class MethodExecutor
# @return [void] # @return [void]
def self.record(level, module_name, message) def self.record(level, module_name, message)
case level case level
when "info" when 'info'
@@logger.info("#{module_name}: #{message}") @@logger.info("#{module_name}: #{message}")
when "warn" when 'warn'
@@logger.warn("#{module_name}: #{message}") @@logger.warn("#{module_name}: #{message}")
when "error" when 'error'
@@logger.error("#{module_name}: #{message}") @@logger.error("#{module_name}: #{message}")
when "debug" when 'debug'
@@logger.debug("#{module_name}: #{message}") @@logger.debug("#{module_name}: #{message}")
else else
raise ArgumentError raise ArgumentError
end end
end end
# rubocop:enable Metrics/MethodLength
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# @return [void] # @return [void]
def self.reset_minecraft_server def self.reset_minecraft_server
record('info', 'reset', "Attempt ##{@@rst_counter + 1} to reset Minecraft server.")
self.record("info", "reset", "Attempt ##{@@rst_counter + 1} to reset Minecraft server.")
if @@rst_counter >= RST_COUNTS_LIMIT if @@rst_counter >= RST_COUNTS_LIMIT
self.record("error", "reset", 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 restarted.")
while true loop do
sleep 10 sleep 10
SystemdNotifier.notify("WATCHDOG=1") if defined?(SystemdNotifier) SystemdNotifier.notify('WATCHDOG=1') if defined?(SystemdNotifier)
if ServerStatusDetector::MinecraftServer.self_diag[:type] == true next unless ServerStatusDetector::MinecraftServer.self_diag[:type] == true
self.record("info", "reset", "Server recovered! Resuming normal monitoring.")
@@rst_counter = 0 record('info', 'reset', 'Server recovered! Resuming normal monitoring.')
return @@rst_counter = 0
end return
end end
end end
@@rst_counter += 1 @@rst_counter += 1
unless Process.euid == 0 unless Process.euid.zero?
self.record("error", "reset", "Permission denied: root required to manage systemd services.") record('error', 'reset', 'Permission denied: root required to manage systemd services.')
raise Errno::EPERM raise Errno::EPERM
end end
self.record("info", "reset", "Spawning 'systemctl restart minecraft' (PID will be captured)...") record('info', 'reset', "Spawning 'systemctl restart minecraft' (PID will be captured)...")
pid = spawn "systemctl restart minecraft" pid = spawn 'systemctl restart minecraft'
self.record("debug", "reset", "systemctl restart spawned with PID #{pid}.") record('debug', 'reset', "systemctl restart spawned with PID #{pid}.")
tick_beg = Time.now tick_beg = Time.now
while true # rubocop:disable Metrics/BlockLength
loop do
sleep 0.5 sleep 0.5
SystemdNotifier.notify("WATCHDOG=1") SystemdNotifier.notify('WATCHDOG=1')
proc_status = Process.waitpid2(pid, Process::WNOHANG) proc_status = Process.waitpid2(pid, Process::WNOHANG)
if proc_status # 子进程已退出,返回 [pid, status] if proc_status # 子进程已退出,返回 [pid, status]
_, status = proc_status _, status = proc_status
elapsed = (Time.now - tick_beg).round(2) elapsed = (Time.now - tick_beg).round(2)
if status.success? if status.success?
self.record("info", "reset", "'systemctl restart' succeeded in #{elapsed}s.") record('info', 'reset', "'systemctl restart' succeeded in #{elapsed}s.")
@@rst_counter = 0 # 成功时重置计数器 @@rst_counter = 0 # 成功时重置计数器
break
else else
self.record("error", "reset", "'systemctl restart' failed with exitcode #{status.exitstatus} after #{elapsed}s.") record('error', 'reset',
break "'systemctl restart' failed with exitcode #{status.exitstatus} after #{elapsed}s.")
end end
break
else # 子进程仍在运行 (WNOHANG 返回 nil) else # 子进程仍在运行 (WNOHANG 返回 nil)
if Time.now - tick_beg > PERSISTENCE_PERIOD then if Time.now - tick_beg > PERSISTENCE_PERIOD
self.record("warn", "reset", record('warn', 'reset',
"'systemctl restart' timed out after #{PERSISTENCE_PERIOD}s. Force-killing PID #{pid}...") "'systemctl restart' timed out after #{PERSISTENCE_PERIOD}s. Force-killing PID #{pid}...")
system "kill -9 #{pid}" system "kill -9 #{pid}"
self.record("warn", "reset", "Running 'systemctl kill minecraft' (last resort)...") record('warn', 'reset', "Running 'systemctl kill minecraft' (last resort)...")
system "systemctl kill minecraft" # 没招了,毁灭吧 system 'systemctl kill minecraft' # 没招了,毁灭吧
sleep 0.5 sleep 0.5
self.record("info", "reset", "Running 'systemctl start minecraft' (rebirth)...") record('info', 'reset', "Running 'systemctl start minecraft' (rebirth)...")
system "systemctl start minecraft" # 重生 system 'systemctl start minecraft' # 重生
self.record("info", "reset", "Force-reset sequence completed.") record('info', 'reset', 'Force-reset sequence completed.')
break break
end end
end end
# rubocop:enable Metrics/BlockLength
end end
end end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
# rubocop:enable Style/ClassVars
end end
+28 -27
View File
@@ -19,12 +19,12 @@ module ServerStatusDetector
require 'timeout' require 'timeout'
class TcpServer class TcpServer
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
# @param host [String] # @param host [String]
# @param port [Integer] # @param port [Integer]
# @param timeout [Integer] # @param timeout [Integer]
# @return [Hash{Symbol=>Integer, Symbol=>Symbol, Symbol=>Float,nil}] # @return [Hash{Symbol=>Integer, Symbol=>Symbol, Symbol=>Float,nil}]
def self.test_port(host = '127.0.0.1', port = 25565, timeout = 10) def self.test_port(host = '127.0.0.1', port = 25_565, timeout = 10)
result = { result = {
port: port, port: port,
status: :unknown, status: :unknown,
@@ -34,12 +34,11 @@ module ServerStatusDetector
# 带错误处理的代码需要使用 begin...end # 带错误处理的代码需要使用 begin...end
begin begin
Timeout.timeout(timeout) do # 此方法接收整数和代码块作为参数,并在超时时引发错误 Timeout.timeout(timeout) do # 此方法接收整数和代码块作为参数,并在超时时引发错误
socket = TCPSocket.new(host,port) socket = TCPSocket.new(host, port)
result[:status] = :open result[:status] = :open
result[:response_time] = (Time.now - begin_time).round(4) result[:response_time] = (Time.now - begin_time).round(4)
socket.close socket.close
end end
rescue Timeout::Error rescue Timeout::Error
result[:status] = :timeout result[:status] = :timeout
rescue Errno::ECONNREFUSED rescue Errno::ECONNREFUSED
@@ -48,66 +47,68 @@ module ServerStatusDetector
result[:status] = :interrupted result[:status] = :interrupted
rescue Errno::ENETUNREACH, Errno::EHOSTUNREACH rescue Errno::ENETUNREACH, Errno::EHOSTUNREACH
result[:status] = :unreachable result[:status] = :unreachable
rescue => error rescue StandardError => e
result[:status] = :error result[:status] = :error
result[:error] = error.message result[:error] = e.message
end end
result result
end end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
end end
class MinecraftServer < TcpServer # 继承为TcpServer编写的属性和方法 # 继承为TcpServer编写的属性和方法
class MinecraftServer < TcpServer
# rubocop:disable Metrics/MethodLength
# @return [Hash{Symbol=>Boolean, Symbol=>Array<String>, Symbol=>String}] # @return [Hash{Symbol=>Boolean, Symbol=>Array<String>, Symbol=>String}]
def self_diag def self_diag
res = test_port('127.0.0.1', 25_565)
res = test_port('127.0.0.1', 25565) unless res[:error].nil?
if res[:error] != nil
message = "Unexpected ERROR when testing SMP by TCP: #{res[:status]},INFO: #{res[:error]}" message = "Unexpected ERROR when testing SMP by TCP: #{res[:status]},INFO: #{res[:error]}"
puts message puts message
return { return {
type: false, type: false,
operation: %w[Record], operation: %w[Record],
message: message, message: message
} }
end # 立刻错误处理是好习惯 # 立刻错误处理是好习惯
end
case res[:status] case res[:status]
when :open when :open
MethodExecutor.record("debug", "self_diag", MethodExecutor.record('debug', 'self_diag',
"TCP check OK — #{res[:response_time]}s") "TCP check OK — #{res[:response_time]}s")
return { {
type: true, type: true
} }
when :timeout when :timeout
MethodExecutor.record("warn", "self_diag", MethodExecutor.record('warn', 'self_diag',
"TCP check timed out.") 'TCP check timed out.')
return { {
type: false, type: false,
operation: %w[Record], operation: %w[Record],
message: "We sent packet without response." message: 'We sent packet without response.'
} }
when :closed when :closed
MethodExecutor.record("warn", "self_diag", MethodExecutor.record('warn', 'self_diag',
"TCP port closed — server is down.") 'TCP port closed — server is down.')
return { {
type: false, type: false,
operation: %w[Record ForceResetServerProcess], # 重启时注意要设置最大尝试次数 operation: %w[Record ForceResetServerProcess], # 重启时注意要设置最大尝试次数
message: "Server died for unknown reason, and will be restarted." message: 'Server died for unknown reason, and will be restarted.'
} }
else else
MethodExecutor.record("warn", "self_diag", MethodExecutor.record('warn', 'self_diag',
"TCP check returned unexpected status: #{res[:status]}.") "TCP check returned unexpected status: #{res[:status]}.")
return { {
type: false, type: false,
operation: %w[Record], operation: %w[Record],
message: "Unexpected status: #{res[:status]}." message: "Unexpected status: #{res[:status]}."
} }
end end
end end
# rubocop:enable Metrics/MethodLength
end end
end end
+15 -13
View File
@@ -14,25 +14,27 @@
# frozen_string_literal: true # frozen_string_literal: true
# Sends keep-alive notifications (including WATCHDOG=1) to systemd
# via the NOTIFY_SOCKET datagram socket, enabling service supervision.
class SystemdNotifier class SystemdNotifier
require_relative 'method_executor' require_relative 'method_executor'
public
@notify_socket = ENV["NOTIFY_SOCKET"]
@watchdog_usec = (ENV["WATCHDOG_USEC"] || '0').to_i
unless @notify_socket && File.socket?(@notify_socket) @notify_socket = ENV.fetch('NOTIFY_SOCKET', nil)
MethodExecutor.record("error", "heartbeat", "NOTIFY_SOCKET is not set or is not a socket, am I a service?") @watchdog_usec = (ENV['WATCHDOG_USEC'] || '0').to_i
@enable_heartbeat = false
else if @notify_socket && File.socket?(@notify_socket)
MethodExecutor.record("debug", "heartbeat", "Found NOTIFY_SOCKET, enabling heartbeat") MethodExecutor.record('debug', 'heartbeat', 'Found NOTIFY_SOCKET, enabling heartbeat')
@enable_heartbeat = true @enable_heartbeat = true
# Fix(2026-06-08): 将 UNIXSocket(SOCK_STREAM) 改为 SOCK_DGRAM # Fix(2026-06-08): 将 UNIXSocket(SOCK_STREAM) 改为 SOCK_DGRAM
# systemd NOTIFY_SOCKET 是 datagram socket,用 stream socket 连接会报 # systemd NOTIFY_SOCKET 是 datagram socket,用 stream socket 连接会报
# Protocol wrong type for socket (Errno::EPROTOTYPE) # Protocol wrong type for socket (Errno::EPROTOTYPE)
@socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM) @socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
@socket.connect(Socket.pack_sockaddr_un(@notify_socket)) @socket.connect(Socket.pack_sockaddr_un(@notify_socket))
MethodExecutor.record("debug", "heartbeat", "Heartbeat socket opened") MethodExecutor.record('debug', 'heartbeat', 'Heartbeat socket opened')
else
MethodExecutor.record('error', 'heartbeat', 'NOTIFY_SOCKET is not set or is not a socket, am I a service?')
@enable_heartbeat = false
end end
# @return [Boolean] # @return [Boolean]
@@ -44,13 +46,13 @@ class SystemdNotifier
# @return [void] # @return [void]
def self.notify(message) def self.notify(message)
return unless @enable_heartbeat return unless @enable_heartbeat
begin begin
@socket.send(message, 0) @socket.send(message, 0)
MethodExecutor.record("debug", "heartbeat", "Heartbeat sent: #{message}") MethodExecutor.record('debug', 'heartbeat', "Heartbeat sent: #{message}")
rescue => e rescue StandardError => e
MethodExecutor.record("error", "heartbeat", "Failed to send heartbeat: #{e}") MethodExecutor.record('error', 'heartbeat', "Failed to send heartbeat: #{e}")
@enable_heartbeat = false @enable_heartbeat = false
end end
end end
end end
+13 -9
View File
@@ -18,7 +18,7 @@ require 'net/http'
require 'uri' require 'uri'
# @type const RELEASE_SERIAL_CODE: String # @type const RELEASE_SERIAL_CODE: String
RELEASE_SERIAL_CODE = 'b43527c' RELEASE_SERIAL_CODE = '60176fa'
# @type const CHANNEL: String # @type const CHANNEL: String
CHANNEL = 'master' CHANNEL = 'master'
# @type const VERSION_LIST_URL: String # @type const VERSION_LIST_URL: String
@@ -27,42 +27,46 @@ VERSION_LIST_URL = "https://raw.giteeusercontent.com/scu_team/minesentinel/#{CHA
RECURSIVE_RETRY_DEPTH = 3 RECURSIVE_RETRY_DEPTH = 3
class UpdateManager class UpdateManager
# rubocop:disable Metrics/MethodLength
# @return [String] # @return [String]
def self.fetch def self.fetch
MethodExecutor.record("debug", "updates", "Fetching version list from #{VERSION_LIST_URL}") MethodExecutor.record('debug', 'updates', "Fetching version list from #{VERSION_LIST_URL}")
uri = URI(VERSION_LIST_URL) uri = URI(VERSION_LIST_URL)
response = Net::HTTP.get_response(uri) response = Net::HTTP.get_response(uri)
case response.code.to_i case response.code.to_i
when 200 when 200
MethodExecutor.record("debug", "updates", "Version list fetched successfully.") MethodExecutor.record('debug', 'updates', 'Version list fetched successfully.')
response.body response.body
else else
raise "Unexpected HTTP response code: #{response.code}" raise "Unexpected HTTP response code: #{response.code}"
end end
rescue => e rescue StandardError => e
MethodExecutor.record("error", "updates", "Failed to fetch version list: #{e}") MethodExecutor.record('error', 'updates', "Failed to fetch version list: #{e}")
nil nil
end end
# rubocop:enable Metrics/MethodLength
# rubocop:disable Metrics/MethodLength
# @return [Hash{Symbol=>Symbol, Symbol=>String}] # @return [Hash{Symbol=>Symbol, Symbol=>String}]
def self.check def self.check
raw = fetch raw = fetch
if raw.nil? if raw.nil?
MethodExecutor.record("error", "updates", "Version list is empty, cannot check updates.") MethodExecutor.record('error', 'updates', 'Version list is empty, cannot check updates.')
raise "Unknown Error" raise 'Unknown Error'
end end
lines = raw.lines(chomp: true) lines = raw.lines(chomp: true)
lines.each_with_index do |line, index| lines.each_with_index do |line, index|
line_num = index + 1 line_num = index + 1
if line == RELEASE_SERIAL_CODE && line_num == lines.size if line == RELEASE_SERIAL_CODE && line_num == lines.size
MethodExecutor.record("info", "updates", "Current version #{RELEASE_SERIAL_CODE} is up to date.") MethodExecutor.record('info', 'updates', "Current version #{RELEASE_SERIAL_CODE} is up to date.")
return { type: :Newest, version: RELEASE_SERIAL_CODE } return { type: :Newest, version: RELEASE_SERIAL_CODE }
end end
end end
latest = lines.last latest = lines.last
MethodExecutor.record("info", "updates", "Newer version #{latest} available (current: #{RELEASE_SERIAL_CODE}).") MethodExecutor.record('info', 'updates', "Newer version #{latest} available (current: #{RELEASE_SERIAL_CODE}).")
{ type: :NewerAvailable, version: latest } { type: :NewerAvailable, version: latest }
end end
# rubocop:enable Metrics/MethodLength
end end