refactor: 修复潜在bug、补全许可声明、添加类型注解、同步文档
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
b43527c
|
||||||
@@ -24,7 +24,8 @@ MineSentinel 是一个 Minecraft 服务器守护进程,每 1.5 秒检测服务
|
|||||||
├── method_executor.rb # 日志系统 + 重启逻辑 + 重试计数器
|
├── method_executor.rb # 日志系统 + 重启逻辑 + 重试计数器
|
||||||
├── server_status_detector.rb # TCP 端口检测模块
|
├── server_status_detector.rb # TCP 端口检测模块
|
||||||
├── systemd_notifier.rb # systemd watchdog 心跳发送
|
├── systemd_notifier.rb # systemd watchdog 心跳发送
|
||||||
└── config.rb # 可调参数配置
|
├── config.rb # 可调参数配置
|
||||||
|
└── updates.rb # 更新检查模块
|
||||||
|
|
||||||
minesentinel.service # systemd 单元文件(项目源码中)
|
minesentinel.service # systemd 单元文件(项目源码中)
|
||||||
```
|
```
|
||||||
@@ -37,7 +38,7 @@ minesentinel.service # systemd 单元文件(项目源码中)
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p /opt/minecraft/sentinel
|
mkdir -p /opt/minecraft/sentinel
|
||||||
cp main.rb method_executor.rb server_status_detector.rb systemd_notifier.rb config.rb /opt/minecraft/sentinel/
|
cp main.rb method_executor.rb server_status_detector.rb systemd_notifier.rb config.rb updates.rb /opt/minecraft/sentinel/
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. 确认 Minecraft systemd 服务存在
|
### 2. 确认 Minecraft systemd 服务存在
|
||||||
|
|||||||
@@ -535,7 +535,7 @@
|
|||||||
<p style="margin-bottom:6px;">方式二:手动复制</p>
|
<p style="margin-bottom:6px;">方式二:手动复制</p>
|
||||||
<pre>mkdir -p /opt/minecraft/sentinel
|
<pre>mkdir -p /opt/minecraft/sentinel
|
||||||
cp main.rb method_executor.rb server_status_detector.rb \
|
cp main.rb method_executor.rb server_status_detector.rb \
|
||||||
systemd_notifier.rb config.rb \
|
systemd_notifier.rb config.rb updates.rb \
|
||||||
/opt/minecraft/sentinel/</pre>
|
/opt/minecraft/sentinel/</pre>
|
||||||
|
|
||||||
<h3>3. 手动测试</h3>
|
<h3>3. 手动测试</h3>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Minecraft 服务器守护进程
|
|||||||
```bash
|
```bash
|
||||||
# 复制文件
|
# 复制文件
|
||||||
mkdir -p /opt/minecraft/sentinel
|
mkdir -p /opt/minecraft/sentinel
|
||||||
cp main.rb method_executor.rb server_status_detector.rb systemd_notifier.rb config.rb /opt/minecraft/sentinel/
|
cp main.rb method_executor.rb server_status_detector.rb systemd_notifier.rb config.rb updates.rb /opt/minecraft/sentinel/
|
||||||
|
|
||||||
# 安装 systemd 服务
|
# 安装 systemd 服务
|
||||||
cp minesentinel.service /etc/systemd/system/
|
cp minesentinel.service /etc/systemd/system/
|
||||||
@@ -63,6 +63,7 @@ MineSentinel 通过 `Before=minecraft.service` 确保先于 MC 启动,避免 M
|
|||||||
| `server_status_detector.rb` | TCP 端口检测模块 |
|
| `server_status_detector.rb` | TCP 端口检测模块 |
|
||||||
| `systemd_notifier.rb` | systemd watchdog 心跳发送 |
|
| `systemd_notifier.rb` | systemd watchdog 心跳发送 |
|
||||||
| `config.rb` | 可调参数配置 |
|
| `config.rb` | 可调参数配置 |
|
||||||
|
| `updates.rb` | 更新检查模块 |
|
||||||
| `minesentinel.service` | systemd 单元文件(独立源码) |
|
| `minesentinel.service` | systemd 单元文件(独立源码) |
|
||||||
|
|
||||||
## 完整文档
|
## 完整文档
|
||||||
|
|||||||
@@ -1,4 +1,24 @@
|
|||||||
|
# <<*>> -^v- SNOWARE APPLICATION
|
||||||
|
# usage: Configurable constants for MineSentinel.
|
||||||
|
# author: S.A.
|
||||||
|
# time: 2026-06-08
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# @type const INITIAL_GRACE: Integer
|
||||||
INITIAL_GRACE = 300 # 初次启动等待 300 秒
|
INITIAL_GRACE = 300 # 初次启动等待 300 秒
|
||||||
|
# @type const TRY_TIMES: Integer
|
||||||
TRY_TIMES = 3 # 尝试次数
|
TRY_TIMES = 3 # 尝试次数
|
||||||
|
# @type const RST_COOLDOWN_DURATION: Integer
|
||||||
RST_COOLDOWN_DURATION = 200 # 重启冷却期 200 秒
|
RST_COOLDOWN_DURATION = 200 # 重启冷却期 200 秒
|
||||||
CHECK_INTERVAL = 1.5 # 检测间隔 1.5 秒
|
# @type const CHECK_INTERVAL: Float
|
||||||
|
CHECK_INTERVAL = 1.5 # 检测间隔 1.5 秒
|
||||||
|
|||||||
@@ -36,6 +36,17 @@ def main
|
|||||||
|
|
||||||
MethodExecutor.record("info", "mainloop", "MineSentinel started, entering mainloop.")
|
MethodExecutor.record("info", "mainloop", "MineSentinel started, entering mainloop.")
|
||||||
|
|
||||||
|
require_relative 'updates'
|
||||||
|
|
||||||
|
begin
|
||||||
|
upd_result = UpdateManager.check
|
||||||
|
if upd_result[:status] == :NewerAvailable
|
||||||
|
MethodExecutor.record("info", "updates", "Update #{upd_result[:version]} available, please check the git repo for details.")
|
||||||
|
end
|
||||||
|
rescue => check_error
|
||||||
|
MethodExecutor.record("error", "updates", "#{check_error.to_s}")
|
||||||
|
end
|
||||||
|
|
||||||
# 初次启动:MineSentinel 先于 minecraft 启动(systemd Before=),
|
# 初次启动:MineSentinel 先于 minecraft 启动(systemd Before=),
|
||||||
# 等待服务器端口就绪,避免误判为崩溃
|
# 等待服务器端口就绪,避免误判为崩溃
|
||||||
MethodExecutor.record("info", "mainloop",
|
MethodExecutor.record("info", "mainloop",
|
||||||
@@ -56,18 +67,18 @@ end
|
|||||||
# Functional mainloop.
|
# Functional mainloop.
|
||||||
def main_once
|
def main_once
|
||||||
chkrslts = []
|
chkrslts = []
|
||||||
for i in 0..TRY_TIMES
|
(0..TRY_TIMES).each do
|
||||||
chkrslt = ServerStatusDetector::MinecraftServer.self_diag
|
chkrslt = ServerStatusDetector::MinecraftServer.self_diag
|
||||||
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
|
||||||
for chkrslt_in in chkrslts
|
chkrslts.each do |result|
|
||||||
MethodExecutor.record("warn", "mainloop", "Server health check FAILED: #{chkrslt_in[: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] == "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.")
|
||||||
@@ -98,6 +109,6 @@ begin
|
|||||||
rescue => e
|
rescue => e
|
||||||
# 强制输出到stderr
|
# 强制输出到stderr
|
||||||
$stderr.puts "[CRITICAL] Unhandled exception: #{e.message}"
|
$stderr.puts "[CRITICAL] Unhandled exception: #{e.message}"
|
||||||
$stderr.puts e.backtrace.join("\n")
|
$stderr.puts e.backtrace&.join("\n")
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ class MethodExecutor
|
|||||||
end
|
end
|
||||||
@@logger.progname = "MineSentinel"
|
@@logger.progname = "MineSentinel"
|
||||||
|
|
||||||
|
# @param level [String] "info" | "warn" | "error" | "debug"
|
||||||
|
# @param module_name [String]
|
||||||
|
# @param message [String]
|
||||||
|
# @return [void]
|
||||||
def self.record(level, module_name, message)
|
def self.record(level, module_name, message)
|
||||||
case level
|
case level
|
||||||
when "info"
|
when "info"
|
||||||
@@ -58,6 +62,7 @@ class MethodExecutor
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [void]
|
||||||
def self.reset_minecraft_server
|
def self.reset_minecraft_server
|
||||||
|
|
||||||
self.record("info", "reset", "Attempt ##{@@rst_counter + 1} to reset Minecraft server.")
|
self.record("info", "reset", "Attempt ##{@@rst_counter + 1} to reset Minecraft server.")
|
||||||
@@ -67,6 +72,7 @@ class MethodExecutor
|
|||||||
"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
|
while true
|
||||||
sleep 10
|
sleep 10
|
||||||
|
SystemdNotifier.notify("WATCHDOG=1") if defined?(SystemdNotifier)
|
||||||
if ServerStatusDetector::MinecraftServer.self_diag[:type] == true
|
if ServerStatusDetector::MinecraftServer.self_diag[:type] == true
|
||||||
self.record("info", "reset", "Server recovered! Resuming normal monitoring.")
|
self.record("info", "reset", "Server recovered! Resuming normal monitoring.")
|
||||||
@@rst_counter = 0
|
@@rst_counter = 0
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ module ServerStatusDetector
|
|||||||
|
|
||||||
class TcpServer
|
class TcpServer
|
||||||
|
|
||||||
|
# @param host [String]
|
||||||
|
# @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 = 25565, timeout = 10)
|
def self.test_port(host = '127.0.0.1', port = 25565, timeout = 10)
|
||||||
result = {
|
result = {
|
||||||
port: port,
|
port: port,
|
||||||
@@ -55,6 +59,7 @@ module ServerStatusDetector
|
|||||||
|
|
||||||
class MinecraftServer < TcpServer # 继承为TcpServer编写的属性和方法
|
class MinecraftServer < TcpServer # 继承为TcpServer编写的属性和方法
|
||||||
|
|
||||||
|
# @return [Hash{Symbol=>Boolean, Symbol=>Array<String>, Symbol=>String}]
|
||||||
def self_diag
|
def self_diag
|
||||||
|
|
||||||
res = test_port('127.0.0.1', 25565)
|
res = test_port('127.0.0.1', 25565)
|
||||||
@@ -64,7 +69,7 @@ module ServerStatusDetector
|
|||||||
puts message
|
puts message
|
||||||
return {
|
return {
|
||||||
type: false,
|
type: false,
|
||||||
operation: ["Record"],
|
operation: %w[Record],
|
||||||
message: message,
|
message: message,
|
||||||
}
|
}
|
||||||
end # 立刻错误处理是好习惯
|
end # 立刻错误处理是好习惯
|
||||||
@@ -82,7 +87,7 @@ module ServerStatusDetector
|
|||||||
"TCP check timed out.")
|
"TCP check timed out.")
|
||||||
return {
|
return {
|
||||||
type: false,
|
type: false,
|
||||||
operation: ["Record"],
|
operation: %w[Record],
|
||||||
message: "We sent packet without response."
|
message: "We sent packet without response."
|
||||||
}
|
}
|
||||||
when :closed
|
when :closed
|
||||||
@@ -90,9 +95,17 @@ module ServerStatusDetector
|
|||||||
"TCP port closed — server is down.")
|
"TCP port closed — server is down.")
|
||||||
return {
|
return {
|
||||||
type: false,
|
type: false,
|
||||||
operation: ["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
|
||||||
|
MethodExecutor.record("warn", "self_diag",
|
||||||
|
"TCP check returned unexpected status: #{res[:status]}.")
|
||||||
|
return {
|
||||||
|
type: false,
|
||||||
|
operation: %w[Record],
|
||||||
|
message: "Unexpected status: #{res[:status]}."
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
+4
-1
@@ -35,17 +35,20 @@ class SystemdNotifier
|
|||||||
MethodExecutor.record("debug", "heartbeat", "Heartbeat socket opened")
|
MethodExecutor.record("debug", "heartbeat", "Heartbeat socket opened")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @return [Boolean]
|
||||||
def enabled?
|
def enabled?
|
||||||
@enable_heartbeat
|
@enable_heartbeat
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @param message [String]
|
||||||
|
# @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 => e
|
||||||
MethodExecutor.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
|
||||||
|
|||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
# <<*>> -^v- SNOWARE APPLICATION
|
||||||
|
# usage: Check for updates from remote.
|
||||||
|
# author: S.A.
|
||||||
|
# time: 2026-06-08
|
||||||
|
# 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
|
||||||
|
|
||||||
|
require 'net/http'
|
||||||
|
require 'uri'
|
||||||
|
|
||||||
|
# @type const RELEASE_SERIAL_CODE: String
|
||||||
|
RELEASE_SERIAL_CODE = 'b43527c'
|
||||||
|
# @type const CHANNEL: String
|
||||||
|
CHANNEL = 'master'
|
||||||
|
# @type const VERSION_LIST_URL: String
|
||||||
|
VERSION_LIST_URL = "https://raw.giteeusercontent.com/scu_team/minesentinel/#{CHANNEL}/APP_VERSIONS_LIST"
|
||||||
|
# @type const RECURSIVE_RETRY_DEPTH: Integer
|
||||||
|
RECURSIVE_RETRY_DEPTH = 3
|
||||||
|
|
||||||
|
class UpdateManager
|
||||||
|
# @return [String]
|
||||||
|
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}"
|
||||||
|
end
|
||||||
|
rescue => e
|
||||||
|
MethodExecutor.record("error", "updates", "Failed to fetch version list: #{e}")
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [Hash{Symbol=>Symbol, Symbol=>String}]
|
||||||
|
def self.check
|
||||||
|
raw = fetch
|
||||||
|
if raw.nil?
|
||||||
|
MethodExecutor.record("error", "updates", "Version list is empty, cannot check updates.")
|
||||||
|
raise "Unknown Error"
|
||||||
|
end
|
||||||
|
|
||||||
|
lines = raw.lines(chomp: true)
|
||||||
|
lines.each_with_index do |line, index|
|
||||||
|
line_num = index + 1
|
||||||
|
if line == RELEASE_SERIAL_CODE && line_num == lines.size
|
||||||
|
MethodExecutor.record("info", "updates", "Current version #{RELEASE_SERIAL_CODE} is up to date.")
|
||||||
|
return { type: :Newest, version: RELEASE_SERIAL_CODE }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
latest = lines.last
|
||||||
|
MethodExecutor.record("info", "updates", "Newer version #{latest} available (current: #{RELEASE_SERIAL_CODE}).")
|
||||||
|
{ type: :NewerAvailable, version: latest }
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user