Preparing for releasing.
This commit is contained in:
@@ -1,140 +1,220 @@
|
||||
# MineSentinel 部署指南
|
||||
|
||||
MineSentinel 是一个 Minecraft 服务器守护进程,每 10 秒检测服务器端口状态,发现崩溃时自动重启。
|
||||
```
|
||||
🛡️ MineSentinel
|
||||
Minecraft 服务器守护进程
|
||||
```
|
||||
|
||||
MineSentinel 是一个 Minecraft 服务器守护进程,每 1.5 秒检测服务器端口状态,发现崩溃后自动重启,重启后进入冷却期(200s)防止循环重启。
|
||||
|
||||
## 环境要求
|
||||
|
||||
- **操作系统**: Linux (systemd)
|
||||
- **Ruby**: >= 3.0(用到了 `Timeout.timeout` 和 `Process.spawn`)
|
||||
- **权限**: root(需要执行 `systemctl` 管理 Minecraft 服务)
|
||||
- **Minecraft 服务**: 已注册为 systemd 服务,服务名为 `minecraft`
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| **操作系统** | Linux(需要 systemd),也支持 Windows(见下方适配指南) |
|
||||
| **Ruby** | >= 3.0 |
|
||||
| **权限** | root(systemd 场景) |
|
||||
| **Minecraft 服务** | 已注册为 systemd 服务,默认服务名 `minecraft` |
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
/opt/minesentinel/ # 推荐部署目录
|
||||
├── main.rb # 主程序入口
|
||||
├── method_executor.rb # 日志系统 + 重启逻辑
|
||||
├── server_status_detector.rb # TCP 端口检测
|
||||
└── uni_log_path.rb # 日志路径工具(可选)
|
||||
/opt/minecraft/sentinel/ # 推荐部署目录
|
||||
├── main.rb # 主程序入口 —— 检测循环 + 冷却期 + 初始宽限期
|
||||
├── method_executor.rb # 日志系统 + 重启逻辑 + 重试计数器
|
||||
└── server_status_detector.rb # TCP 端口检测模块
|
||||
|
||||
minesentinel.service # systemd 单元文件(项目源码中)
|
||||
```
|
||||
|
||||
> `uni_log_path.rb` 已废弃。
|
||||
|
||||
## 部署步骤
|
||||
|
||||
### 1. 创建目录并复制文件
|
||||
|
||||
```bash
|
||||
mkdir -p /opt/minesentinel
|
||||
cp main.rb method_executor.rb server_status_detector.rb /opt/minesentinel/
|
||||
mkdir -p /opt/minecraft/sentinel
|
||||
cp main.rb method_executor.rb server_status_detector.rb /opt/minecraft/sentinel/
|
||||
```
|
||||
|
||||
如果要用 `uni_log_path.rb` 也一并复制。
|
||||
|
||||
### 2. 确认 Minecraft systemd 服务存在
|
||||
|
||||
```bash
|
||||
systemctl status minecraft
|
||||
```
|
||||
|
||||
如果你的 Minecraft 服务名不是 `minecraft`,需要修改 `method_executor.rb` 中所有 `systemctl xxx minecraft` 为你的实际服务名。
|
||||
如果服务名不是 `minecraft`,修改 `method_executor.rb` 中所有 `systemctl xxx minecraft` 为实际服务名(共 3 处:restart / kill / start),同时修改 `minesentinel.service` 中的 `Before=minecraft.service` 和 `Wants=minecraft.service`。
|
||||
|
||||
### 3. 手动测试
|
||||
|
||||
先前台跑一次确认无报错:
|
||||
|
||||
```bash
|
||||
cd /opt/minesentinel
|
||||
cd /opt/minecraft/sentinel
|
||||
ruby main.rb
|
||||
# 调试模式:LOG_LEVEL=debug ruby main.rb
|
||||
```
|
||||
|
||||
观察日志输出,确认能正常检测端口、识别服务状态。按 `Ctrl+C` 退出。
|
||||
### 4. 注册 systemd 服务
|
||||
|
||||
调试模式(输出更详细):
|
||||
项目源码中已提供 `minesentinel.service`,直接复制:
|
||||
|
||||
```bash
|
||||
LOG_LEVEL=debug ruby main.rb
|
||||
cp minesentinel.service /etc/systemd/system/
|
||||
```
|
||||
|
||||
### 4. 注册为 systemd 服务
|
||||
|
||||
创建 `/etc/systemd/system/minesentinel.service`:
|
||||
单元文件内容(`minesentinel.service`):
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=MineSentinel - Minecraft Server Auto-Restart Daemon
|
||||
Description=MineSentinel Minecraft Server Monitor
|
||||
After=network.target
|
||||
Before=minecraft.service
|
||||
Wants=minecraft.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/minesentinel
|
||||
ExecStart=/usr/bin/ruby /opt/minesentinel/main.rb
|
||||
WorkingDirectory=/opt/minecraft/sentinel
|
||||
ExecStart=/usr/bin/ruby /opt/minecraft/sentinel/main.rb
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
Environment=LOG_LEVEL=info
|
||||
#Environment=LOG_LEVEL=debug
|
||||
#Environment=RUBY_DEBUG=1
|
||||
WatchdogSec=210
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
然后:
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `Before=minecraft.service` | MineSentinel 先于 MC 启动,消除守护盲区 |
|
||||
| `Wants=minecraft.service` | 软依赖 MC 服务,MC 启动失败不影响 MineSentinel |
|
||||
| `RestartSec=10` | MineSentinel 自身崩溃后 10s 重启 |
|
||||
| `WatchdogSec=210` | 必须大于冷却期(200s),防止 watchdog 误杀 |
|
||||
|
||||
启动顺序:
|
||||
|
||||
```
|
||||
开机 → network.target → MineSentinel → (300s 宽限期) → minecraft 启动 → 端口就绪
|
||||
```
|
||||
|
||||
```bash
|
||||
systemctl daemon-reload
|
||||
systemctl enable minesentinel
|
||||
systemctl enable minesentinel # 同时也会触发 minecraft(Wants=)
|
||||
systemctl start minesentinel
|
||||
```
|
||||
|
||||
### 5. 查看日志
|
||||
|
||||
```bash
|
||||
# 实时日志
|
||||
journalctl -u minesentinel -f
|
||||
|
||||
# 最近 50 条
|
||||
journalctl -u minesentinel -n 50
|
||||
|
||||
# 只看错误
|
||||
journalctl -u minesentinel -p err
|
||||
journalctl -u minesentinel -f # 实时
|
||||
journalctl -u minesentinel -u minecraft -f # 双服务合并
|
||||
```
|
||||
|
||||
## 日志级别说明
|
||||
|
||||
通过 `Environment=LOG_LEVEL=xxx` 控制:
|
||||
|
||||
| 级别 | 说明 |
|
||||
|------|------|
|
||||
| `debug` | 全部输出,含每次巡检结果、TCP 响应时间 |
|
||||
| `info` | 启动/重启/恢复等关键操作 |
|
||||
| `warn` | 服务器异常、超时、强制终止 |
|
||||
| `error` | 重启失败、重试次数耗尽 |
|
||||
|
||||
默认 `info`,生产环境推荐 `info`;排查问题时临时改为 `debug`。
|
||||
|
||||
## 工作流程
|
||||
|
||||
```
|
||||
每 10 秒循环:
|
||||
MineSentinel 启动:
|
||||
├─ 进入初始宽限期(300s),等待 MC 端口就绪
|
||||
└─ 宽限期结束 → 进入正常监控循环
|
||||
|
||||
每 1.5 秒循环:
|
||||
├─ TCP 连接 127.0.0.1:25565
|
||||
├─ 端口开放 → debug 日志,继续等待
|
||||
├─ 端口关闭 → 记录 warn,触发重启
|
||||
│ └─ systemctl restart minecraft(最多重试 10 次)
|
||||
│ ├─ 30 秒内完成 → 成功,计数器归零
|
||||
│ └─ 超时 → kill -9 → systemctl kill → systemctl start
|
||||
└─ 10 次全部失败 → 进入空闲等待,直到手动恢复
|
||||
├─ 端口开放 → 继续循环
|
||||
├─ 端口超时 → 仅记录日志(不重启,可能是网络抖动)
|
||||
└─ 端口关闭 → 触发重启 → 冷却 200 秒等待启动
|
||||
└─ 连续失败 10 次 → 进入空闲等待,直到手动恢复
|
||||
```
|
||||
|
||||
## 可调参数
|
||||
|
||||
| 参数 | 文件 | 行 | 默认值 |
|
||||
|------|------|-----|--------|
|
||||
| 初始宽限期 | `main.rb` | 31 | `300` s |
|
||||
| 检测间隔 | `main.rb` | 69 | `1.5` s |
|
||||
| 冷却期 | `main.rb` | 58 | `200` s |
|
||||
| systemctl 超时 | `method_executor.rb` | 17 | `30` s |
|
||||
| 最大重试 | `method_executor.rb` | 23 | `10` 次 |
|
||||
| 检测端口 | `server_status_detector.rb` | 60 | `25565` |
|
||||
| TCP 超时 | `server_status_detector.rb` | 23 | `10` s |
|
||||
|
||||
## 日志级别
|
||||
|
||||
`Environment=LOG_LEVEL=xxx`,默认 `debug`(未设置时),生产推荐 `info`。
|
||||
|
||||
| 级别 | 内容 |
|
||||
|------|------|
|
||||
| `debug` | 每次巡检、TCP 响应时间、systemctl PID |
|
||||
| `info` | 启动/宽限期/重启/恢复/冷却期 |
|
||||
| `warn` | 端口超时/关闭、强制终止 |
|
||||
| `error` | 重启失败、重试耗尽 |
|
||||
|
||||
## 跨场景适配指南
|
||||
|
||||
### 无 systemd(Docker / screen / 手动启动)
|
||||
|
||||
核心:替换 `method_executor.rb` 第 86 行的重启命令,并移除第 80-83 行 root 权限检查。
|
||||
|
||||
**Docker:**
|
||||
```ruby
|
||||
pid = spawn "docker restart mc-server"
|
||||
```
|
||||
|
||||
**screen:**
|
||||
```ruby
|
||||
system "screen -S mc -X quit"
|
||||
sleep 2
|
||||
system "screen -dmS mc java -Xmx4G -jar server.jar nogui"
|
||||
```
|
||||
|
||||
**supervisor:**
|
||||
```ruby
|
||||
pid = spawn "supervisorctl restart minecraft"
|
||||
```
|
||||
|
||||
### Windows 环境
|
||||
|
||||
核心逻辑跨平台,仅需适配重启方式。`method_executor.rb` 修改:
|
||||
|
||||
```ruby
|
||||
# 删除第 80-83 行 root 权限检查(Windows 无 Process.euid)
|
||||
|
||||
# 第 86 行替换为(直接调 Java 或 bat):
|
||||
pid = spawn "cmd /c start /B java -Xmx4G -jar C:\\mc\\server.jar nogui"
|
||||
```
|
||||
|
||||
日志输出到文件(Windows 无 journal)—— 在 `method_executor.rb` 第 26 行改为:
|
||||
|
||||
```ruby
|
||||
@@logger = Logger.new("C:\\mc\\sentinel.log", "daily")
|
||||
```
|
||||
|
||||
以管理员身份运行终端,`ruby main.rb` 即可。
|
||||
|
||||
### 监控其他服务
|
||||
|
||||
`server_status_detector.rb` 的 `test_port` 是通用 TCP 检测,改端口和 IP 即可监控任意服务:
|
||||
|
||||
```ruby
|
||||
# server_status_detector.rb 第 60 行
|
||||
res = test_port('192.168.1.100', 3306) # MySQL
|
||||
res = test_port('127.0.0.1', 80) # Nginx
|
||||
```
|
||||
|
||||
配合修改 `method_executor.rb` 中的重启命令即可适配。
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: 报 `Errno::EPERM`?**
|
||||
A: 需要 root 权限执行。确认 systemd service 以 root 运行,或 `sudo ruby main.rb`。
|
||||
**Q: 报 `Errno::EPERM`?** A: 需 root 权限或 `sudo ruby main.rb`。
|
||||
|
||||
**Q: 如何修改检测端口?**
|
||||
A: 编辑 `server_status_detector.rb` 第 52 行,修改 `test_port` 的端口参数。
|
||||
**Q: 端口一直超时但不重启?** A: 设计如此。`:timeout` 只记录不重启,仅 `:closed` 触发重启。
|
||||
|
||||
**Q: 如何修改检测间隔?**
|
||||
A: 编辑 `main.rb` 第 33 行,修改 `sleep 10` 的值。
|
||||
**Q: 启动时 MineSentinel 反复重启 minecraft?** A: 检查 `INITIAL_GRACE` 是否小于 MC 实际启动时间,适当调大。
|
||||
|
||||
**Q: 如何修改重启超时时间?**
|
||||
A: 编辑 `method_executor.rb` 第 9 行,修改 `PERSISTENCE_PERIOD`。
|
||||
**Q: 如何修改 Minecraft 服务名?** A: 编辑 `method_executor.rb`,搜索 `systemctl xxx minecraft`(3 处),替换 `minecraft` 为实际服务名。同时修改 `minesentinel.service` 中的 `Before=` 和 `Wants=`。
|
||||
|
||||
## 许可证
|
||||
|
||||
MineSentinel 源码采用 [木兰宽松许可证 v2](http://license.coscl.org.cn/MulanPubL-2.0)。品牌标识(Logo / 项目名称)保留所有权利,未经许可不得用于衍生项目。
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
木兰公共许可证, 第2版
|
||||
|
||||
木兰公共许可证, 第2版
|
||||
|
||||
2021年5月 http://license.coscl.org.cn/MulanPubL-2.0
|
||||
|
||||
您对“贡献”的复制、使用、修改及分发受木兰公共许可证,第2版(以下简称“本许可证”)的如下条款的约束:
|
||||
|
||||
0. 定义
|
||||
|
||||
“贡献” 是指由“贡献者”许可在“本许可证”下的受版权法保护的作品,包括最初“贡献者”许可在“本许可证”下的作品及后续“贡献者”许可在“本许可证”下的“衍生作品”。
|
||||
|
||||
“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
|
||||
|
||||
“法人实体” 是指提交贡献的机构及其“关联实体”。
|
||||
|
||||
“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的“控制”是指拥有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
|
||||
|
||||
“衍生作品” 是指基于“贡献”创作的作品,具体包括对全部或部分“贡献”进行修改、重写、翻译、注释、组合或与之链接(包括动态链接或静态链接)而形成的作品。仅与“贡献”进行进程间通信或系统调用的作品是独立作品,不属于“衍生作品”。
|
||||
|
||||
“对应源代码” 是指生成、安装和(对于可执行作品)运行目标代码所需的所有源文件和与之关联的接口定义文件,以及控制这些活动的脚本,但不包括编译环境、编译工具、云服务平台(如果有)。
|
||||
|
||||
“分发” 是指通过任何媒介向他人提供“贡献”或“衍生作品”的行为,以及利用“贡献”或“衍生作品”通过网络远程给用户提供服务的行为,例如:通过利用“贡献”或“衍生作品”搭建的云服务平台提供在线服务的行为。
|
||||
|
||||
1. 授予版权许可
|
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、“分发”其“贡献”或“衍生作品”,不论修改与否。
|
||||
|
||||
2. 授予专利许可
|
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销的情形除外)专利许可,供您使用、制造、委托制造、销售、许诺销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”中的专利权利要求,而不包括仅因您对“贡献”的修改而将必然会侵犯到的专利权利要求。如果您或您的“关联实体”直接或间接地,就“贡献”对任何人发起专利侵权诉讼(包括在诉讼中提出反诉请求或交叉请求)或发起其他专利维权行动,则“贡献者”根据“本许可证”授予您的专利许可自您发起专利诉讼或专利维权行动之日终止。
|
||||
|
||||
3. 无商标许可
|
||||
|
||||
“贡献者”在“本许可证”下不提供对其商品名称、商标、服务标识或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用的情形除外。
|
||||
|
||||
4. 分发限制
|
||||
|
||||
您可以将您接收到的“贡献”或您的“衍生作品”以源程序形式或可执行形式重新“分发”,但必须满足下列条件:
|
||||
|
||||
(1)您必须向接收者提供“本许可证”的副本,并保留“贡献”中的版权、商标、专利及免责声明;并且,
|
||||
|
||||
(2)如果您“分发”您接收到的“贡献”,您必须使用“本许可证”提供该“贡献”的源代码副本;如果您 “分发”您的“衍生作品”,您必须:
|
||||
|
||||
(i)随“衍生作品”提供使用“本许可证”“分发”的您的“衍生作品”的“对应源代码”。如果您通过下载链接提供前述“对应源代码”,则您应将下载链接地址置于“衍生作品”或其随附文档中的明显位置,有效期自该“衍生作品”“分发”之日起不少于三年,并确保接收者可以获得“对应源代码”;或者,
|
||||
|
||||
(ii)随“衍生作品”向接收者提供一个书面要约,表明您愿意提供根据“本许可证”“分发”的您“衍生作品”的“对应源代码”。该书面要约应置于“衍生作品”中的明显位置,并确保接收者根据书面要约可获取“对应源代码”的时间从您接到该请求之日起不得超过三个月,且有效期自该“衍生作品”“分发”之日起不少于三年。
|
||||
|
||||
5. 违约与终止
|
||||
|
||||
如果您违反“本许可证”,任何“贡献者”有权书面通知您终止其根据“本许可证”授予您的许可。该“贡献者”授予您的许可自您接到其终止通知之日起终止。仅在如下两种情形下,即使您收到“贡献者”的通知也并不终止其授予您的许可:
|
||||
|
||||
(1)您在接到该终止通知之前已停止所有违反行为;
|
||||
|
||||
(2)您是首次收到该“贡献者”根据“本许可证”发出的书面终止通知,并且您在收到该通知后30天内已停止所有违反行为。
|
||||
|
||||
只要您下游的接收者遵守“本许可证”的相关规定,即使您在“本许可证”下被授予的许可终止,不影响下游的接收者根据“本许可证”享有的权利。
|
||||
|
||||
6. 例外
|
||||
|
||||
如果您将“贡献”与采用GNU AFFERO GENERAL PUBLIC LICENSE Version 3(以下简称“AGPLv3”)或其后续版本的作品结合形成新的“衍生作品”,且根据“AGPLv3”或其后续版本的要求您有义务将新形成的“衍生作品”以“AGPLv3”或其后续版本进行许可的,您可以根据“AGPLv3”或其后续版本进行许可,只要您在“分发”该“衍生作品”的同时向接收者提供“本许可证”的副本,并保留“贡献”中的版权、商标、专利及免责声明。但任何“贡献者”不会因您选择“AGPLv3”或其后续版本而授予该“衍生作品”的接收者更多权利。
|
||||
|
||||
7. 免责声明与责任限制
|
||||
|
||||
“贡献”在提供时不带有任何明示或默示的担保。在任何情况下,“贡献者”或版权人不对任何人因使用“贡献”而引发的任何直接或间接损失承担任何责任,不论该等损失因何种原因导致或者基于何种法律理论,即使其曾被告知有该等损失的可能性。
|
||||
|
||||
8. 语言
|
||||
|
||||
“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何不一致,以中文版为准。
|
||||
|
||||
条款结束
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
Mulan Public License,Version 2
|
||||
|
||||
Mulan Public License,Version 2 (Mulan PubL v2)
|
||||
|
||||
May 2021 http://license.coscl.org.cn/MulanPubL-2.0
|
||||
|
||||
Your reproduction, use, modification and Distribution of the Contribution shall be subject to Mulan Public License, Version 2 (this License) with following terms and conditions:
|
||||
|
||||
0. Definition
|
||||
|
||||
Contribution means the copyrightable work licensed by a particular Contributor under this License, including the work licensed by the initial Contributor under this License and its Derivative Work licensed by any subsequent Contributor under this License.
|
||||
|
||||
Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
|
||||
|
||||
Legal Entity means the entity making a Contribution and all its Affiliates.
|
||||
|
||||
Affiliates mmeans entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
|
||||
|
||||
Derivative Work means works created based on Contribution, specifically including works formed by modifying, rewriting, translating, annotating, combining or linking to all or part of Contribution (including dynamic linking or static linking). Works which only communicate with Contribution through inter-process communication or system call, are independent works, rather than Derivative Work.
|
||||
|
||||
Corresponding Source Code means all the source code needed to generate, install, and (for an executable work) run the object code including the interface definition files associated with source files for the work, and scripts to control those activities, excluding of compilation environment and compilation tools, cloud services platform (if any).
|
||||
|
||||
Distribute (or Distribution) means the act of making the Contribution or Derivative Work available to others through any medium, and using the Contribution or Derivative Work to provide online services to users, such as the act of providing online services through a cloud service platform built using Contributions or Derivative Works.
|
||||
|
||||
1. Grant of Copyright License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or Distribute its Contribution or Derivative Work, with modification or not.
|
||||
|
||||
2. Grant of Patent License
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to use, make, have made, sell, offer for sale, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, excluding of any patent claims solely be infringed by your modification. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that any Contribution infringes patents, then any patent license granted to you under this License for the Contribution shall terminate as of the date such litigation or activity is filed or taken.
|
||||
|
||||
3. No Trademark License
|
||||
|
||||
No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
|
||||
|
||||
4. Distribution Restriction
|
||||
|
||||
You may Distribute the Contribution you received or your Derivative Work, whether in source or executable forms, provided that you meet the following conditions:
|
||||
|
||||
1) You must provide recipients with a copy of this License and retain copyright, trademark, patent and disclaimer statements in the Contribution; and,
|
||||
|
||||
2) If you Distribute the Contribution you received, you must provide copies of the Contribution’s source code under this License;
|
||||
|
||||
If you Distribute your Derivative Work, you have to:
|
||||
|
||||
(i) accompanying the Derivative work, provide recipients with Corresponding Source Code of your Derivative Work under this License. If you provide the Corresponding Source Code through a download link, you should place such link address prominently in the Derivative Work or its accompanying documents, and be valid no less than three years from your Distribution of the particular Derivative Work, and ensure that the recipients can acquire the Corresponding Source Code through the link; or,
|
||||
|
||||
(ii) accompanying the Derivative Work, provide recipients with a written offer indicating your willingness to provide the Corresponding Source Code of the Derivative Work licensed under this License. Such written offer shall be placed prominently in the Derivative Work or its accompanying documents. Without reasonable excuse, the recipient shall be able to acquire the Corresponding Source code of the Derivative work for no more than three months from your receipt of a valid request, and be valid no less than three years from your Distribution of the particular Derivative Work.
|
||||
|
||||
5. Breach and Termination
|
||||
|
||||
If you breach this License, any Contributor has the right to notify you in writing to terminate its license granted to you under this License. The license granted to you by such Contributor terminates upon your receipt of such notice of termination. Notwithstanding the foregoing, your license will not be terminated even if you receive a notice of termination from Contributor, provided that:
|
||||
|
||||
1) you have cured all the breaches prior to receiving such notice of termination; or,
|
||||
|
||||
2) it’s your first time to receive a notice of termination from such Contributor pursuant to this License, and you have cured all the breaches within 30 days of receipt of such notice.
|
||||
|
||||
Termination of your license under this License shall not affect the downstream recipient's rights under this License, provided that the downstream recipient complies with this License.
|
||||
|
||||
6. Exceptions
|
||||
|
||||
If you combine Contribution or your Derivative Work with a work licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (hereinafter referred to as “AGPLv3”) or its subsequent versions, and according to the AGPLv3 or its subsequent versions, you have an obligation to make the combined work to be licensed under the corresponding license, you can license such combined work under the license, provided that when you Distribute the combined work, you also provide a copy of this License to the recipients, and retain copyright, trademarks, patents, and disclaimer statements in the Contribution. No Contributor will grant additional rights to the recipients of the combined work for your license under AGPLv3 or its subsequent versions.
|
||||
|
||||
7. Disclaimer of Warranty and Limitation of liability
|
||||
|
||||
CONTRIBUTION ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE CONTRIBUTION, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
8. Language
|
||||
|
||||
THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
|
||||
|
||||
END OF THE TERMS AND CONDITIONS
|
||||
@@ -0,0 +1,31 @@
|
||||
[[projects]]
|
||||
name = "MineSentinel"
|
||||
description = "Minecraft 服务器智能守护进程,TCP 端口检测 + 崩溃自动重启 + 防循环冷却期"
|
||||
repo_url = "https://gitee.com/snoware/minesentinel"
|
||||
demo_url = "https://swe-iss.rth1.xyz/softwares/minesentinel"
|
||||
doc_url = "https://gitee.com/snoware/minesentinel/blob/master/DEPLOY.md"
|
||||
tags = ["Ruby", "Minecraft", "Server", "Monitoring", "Daemon", "SNOWARE"]
|
||||
|
||||
[[projects]]
|
||||
name = "btcli"
|
||||
description = "一款在线命令行翻译工具,支持中文及其他语言,配备TUI界面"
|
||||
repo_url = "https://gitee.com/ironbayberry/btcli"
|
||||
demo_url = "https://swe-iss.rth1.xyz/softwares/btcli"
|
||||
doc_url = "https://docs.rs/btcli"
|
||||
tags = ["Rust", "CLI", "Translation", "TUI", "SASWE"]
|
||||
|
||||
[[projects]]
|
||||
name = "Reverberation"
|
||||
description = "Story teller, it's more lightweight than nano and more convenient to use than echo."
|
||||
repo_url = "https://gitee.com/ironbayberry/reverberation"
|
||||
demo_url = "https://swe-iss.rth1.xyz/softwares/reverberation"
|
||||
doc_url = "https://docs.rs/reverberation"
|
||||
tags = ["CLI", "Chinese", "open-source", "SASWE", "Rust"]
|
||||
|
||||
[[projects]]
|
||||
name = "Mint Runtime"
|
||||
description = "Security-featured runtime for Lua with i18n support, bytecode verification, and fine-grained access control."
|
||||
repo_url = "https://gitee.com/snoware/mint"
|
||||
demo_url = "https://swe-iss.rth1.xyz/softwares/mint"
|
||||
doc_url = "https://docs.rs/mintrt"
|
||||
tags = ["CLI", "Chinese", "open-source", "SASWE", "Rust", "Lua", "Runtime", "Security", "i18n"]
|
||||
@@ -0,0 +1,674 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
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.
|
||||
品牌标识(Logo / SVG / 项目名称" MineSentinel")保留所有权利,未经许可不得用于衍生项目。
|
||||
-->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MineSentinel — Minecraft 服务器守护进程</title>
|
||||
<script src='https://gitee.com/SCU_team/mine-sentinel/widget_preview' async defer></script>
|
||||
<style>
|
||||
:root {
|
||||
--amber: #f59e0b;
|
||||
--orange: #ea580c;
|
||||
--coral: #e8613c;
|
||||
--warm-bg: #ffedd5;
|
||||
--card-bg: #fff7ed;
|
||||
--text: #431407;
|
||||
--text-dim: #7c2d12;
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.7;
|
||||
color: var(--text);
|
||||
background: linear-gradient(160deg, #f97316 0%, #ea580c 40%, #dc2626 100%);
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Geometric background elements */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background:
|
||||
repeating-linear-gradient(30deg, transparent, transparent 80px, rgba(255,255,255,0.03) 80px, rgba(255,255,255,0.03) 82px),
|
||||
repeating-linear-gradient(-60deg, transparent, transparent 120px, rgba(255,255,255,0.04) 120px, rgba(255,255,255,0.04) 122px);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.geo-hex {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
opacity: 0.06;
|
||||
}
|
||||
.geo-hex::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 300px; height: 520px;
|
||||
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
|
||||
background: #fff;
|
||||
}
|
||||
.geo-hex-1 { top: -100px; right: -80px; transform: rotate(15deg); }
|
||||
.geo-hex-2 { bottom: 10%; left: -120px; transform: rotate(-10deg) scale(0.7); }
|
||||
.geo-hex-3 { top: 60%; right: 5%; transform: rotate(45deg) scale(0.4); }
|
||||
|
||||
.geo-tri {
|
||||
position: fixed;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
opacity: 0.05;
|
||||
}
|
||||
.geo-tri::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0; height: 0;
|
||||
border-left: 200px solid transparent;
|
||||
border-right: 200px solid transparent;
|
||||
border-bottom: 350px solid #fff;
|
||||
}
|
||||
.geo-tri-1 { top: 30%; left: 5%; transform: rotate(-20deg); }
|
||||
.geo-tri-2 { bottom: 20%; right: 10%; transform: rotate(30deg) scale(0.6); }
|
||||
|
||||
.container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
padding: 70px 20px 40px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.logo-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
margin: 0 auto 20px;
|
||||
filter: drop-shadow(0 4px 12px rgba(0,0,0,0.35));
|
||||
}
|
||||
.logo-svg {
|
||||
width: 100px;
|
||||
display: block;
|
||||
}
|
||||
.logo-png {
|
||||
width: 80px;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
.author-badge {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.75;
|
||||
margin-top: 4px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.author-badge strong {
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 12px;
|
||||
text-shadow: 0 3px 6px rgba(0,0,0,0.4);
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.4rem;
|
||||
opacity: 0.92;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-size: 1rem;
|
||||
letter-spacing: 4px;
|
||||
font-weight: 600;
|
||||
margin-top: 16px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Widget area */
|
||||
.widget-container {
|
||||
margin-bottom: 50px;
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
background: rgba(67, 20, 7, 0.85);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
||||
}
|
||||
.osc_pro_color { color: #ffffff !important; }
|
||||
.osc_panel_color { background-color: #1e252b !important; }
|
||||
.osc_background_color { background-color: #323d47 !important; }
|
||||
.osc_border_color { border-color: #455059 !important; }
|
||||
.osc_desc_color { color: #d7deea !important; }
|
||||
.osc_link_color * { color: #99a0ae !important; }
|
||||
|
||||
/* Log Preview */
|
||||
.log-preview-section {
|
||||
max-width: 860px;
|
||||
margin: 50px auto;
|
||||
padding: 30px;
|
||||
}
|
||||
.log-preview-section h2 {
|
||||
text-align: center;
|
||||
font-size: 1.9rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.log-desc {
|
||||
text-align: center;
|
||||
color: var(--warm-bg);
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.log-terminal {
|
||||
background: #1a1b26;
|
||||
border: 1px solid #323d47;
|
||||
border-radius: 12px;
|
||||
padding: 18px 22px;
|
||||
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Consolas', monospace;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.75;
|
||||
box-shadow: inset 0 2px 12px rgba(0,0,0,0.5), 0 4px 16px rgba(0,0,0,0.25);
|
||||
}
|
||||
.log-line {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #d7deea;
|
||||
}
|
||||
.log-time {
|
||||
color: #565f89;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.log-level {
|
||||
display: inline-block;
|
||||
min-width: 48px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.log-level.info { color: #a9b665; }
|
||||
.log-level.warn { color: #e0af68; }
|
||||
.log-level.debug { color: #7dcfff; }
|
||||
.log-level.error { color: #f7768e; }
|
||||
|
||||
/* Cards */
|
||||
.section-title {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 2rem;
|
||||
margin: 50px 0 30px;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 24px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 28px;
|
||||
box-shadow: 0 8px 28px rgba(0,0,0,0.18);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
border-top: 4px solid var(--orange);
|
||||
}
|
||||
.feature-card:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 14px 36px rgba(0,0,0,0.25);
|
||||
}
|
||||
.feature-card h3 {
|
||||
color: var(--orange);
|
||||
font-size: 1.35rem;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.feature-card p {
|
||||
color: var(--text-dim);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
/* Deploy tutorial */
|
||||
.deploy-section {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
box-shadow: 0 8px 28px rgba(0,0,0,0.18);
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.deploy-section h2 {
|
||||
color: var(--orange);
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 3px solid var(--amber);
|
||||
}
|
||||
.deploy-section h3 {
|
||||
color: var(--text);
|
||||
margin: 24px 0 10px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.deploy-section h4 {
|
||||
color: var(--orange);
|
||||
margin: 18px 0 8px;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.deploy-section pre {
|
||||
background: #1c1917;
|
||||
color: #fcd34d;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
margin: 10px 0 16px;
|
||||
}
|
||||
.deploy-section code {
|
||||
background: #fff7ed;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
color: var(--orange);
|
||||
font-size: 0.92em;
|
||||
}
|
||||
.deploy-section table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 12px 0 20px;
|
||||
}
|
||||
.deploy-section th, .deploy-section td {
|
||||
padding: 10px 14px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #fed7aa;
|
||||
}
|
||||
.deploy-section th {
|
||||
background: #ffedd5;
|
||||
color: var(--orange);
|
||||
font-weight: 600;
|
||||
}
|
||||
.deploy-section ul { margin: 8px 0 16px 20px; }
|
||||
.deploy-section li { margin: 4px 0; color: var(--text-dim); }
|
||||
|
||||
/* Flow diagram */
|
||||
.flow-box {
|
||||
background: #1c1917;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
color: #fcd34d;
|
||||
font-family: monospace;
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.9;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.cta-section {
|
||||
text-align: center;
|
||||
margin: 50px 0;
|
||||
padding: 30px 20px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 14px 36px;
|
||||
background: white;
|
||||
color: var(--orange);
|
||||
text-decoration: none;
|
||||
border-radius: 30px;
|
||||
font-weight: bold;
|
||||
font-size: 1.05rem;
|
||||
margin: 8px;
|
||||
box-shadow: 0 4px 14px rgba(0,0,0,0.2);
|
||||
transition: all 0.3s;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
.btn:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 8px 22px rgba(0,0,0,0.35);
|
||||
}
|
||||
.btn-primary {
|
||||
background: #f97316;
|
||||
color: white;
|
||||
}
|
||||
.btn-primary:hover { background: #ea580c; }
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.modal.show { display: flex; }
|
||||
.modal-content {
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 36px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
|
||||
position: relative;
|
||||
animation: modalIn 0.3s ease-out;
|
||||
}
|
||||
@keyframes modalIn {
|
||||
from { transform: translateY(-40px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
top: 12px; right: 18px;
|
||||
font-size: 28px;
|
||||
cursor: pointer;
|
||||
color: #9a3412;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
.modal-close:hover { color: #431407; }
|
||||
.modal-title { color: var(--orange); font-size: 1.6rem; margin-bottom: 18px; }
|
||||
.modal-body { color: var(--text-dim); line-height: 1.8; margin-bottom: 20px; }
|
||||
.download-info {
|
||||
background: #ffedd5;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--orange);
|
||||
}
|
||||
.download-link { color: var(--orange); text-decoration: none; font-weight: bold; }
|
||||
.download-link:hover { text-decoration: underline; }
|
||||
.password-info {
|
||||
margin-top: 14px;
|
||||
padding: 10px;
|
||||
background: #fff3cd;
|
||||
border-radius: 6px;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: white;
|
||||
opacity: 0.8;
|
||||
margin-top: 40px;
|
||||
}
|
||||
.links { margin-top: 14px; }
|
||||
.links a {
|
||||
color: white; text-decoration: none; margin: 0 14px; opacity: 0.9;
|
||||
}
|
||||
.links a:hover { opacity: 1; text-decoration: underline; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
h1 { font-size: 2rem; }
|
||||
.deploy-section { padding: 20px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Geometric decorations -->
|
||||
<div class="geo-hex geo-hex-1"></div>
|
||||
<div class="geo-hex geo-hex-2"></div>
|
||||
<div class="geo-hex geo-hex-3"></div>
|
||||
<div class="geo-tri geo-tri-1"></div>
|
||||
<div class="geo-tri geo-tri-2"></div>
|
||||
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="logo-row">
|
||||
<img class="logo-png" src="swsec.png" alt="SW Security">
|
||||
<svg class="logo-svg" viewBox="0 0 100 80" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0,1 H100" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
|
||||
<path d="M0,20 H30" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
|
||||
<path d="M0,40 H30 L40,20 L60,60 L70,40 H100" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
|
||||
<path d="M70,60 H100" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
|
||||
<path d="M0,79 H100" stroke="#ffffff" stroke-width="6" fill="none" opacity="0.9"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1>🛡️ MineSentinel</h1>
|
||||
<p class="subtitle">Minecraft 服务器智能守护进程</p>
|
||||
<p class="tagline">TCP 检测 · 自动重启 · 防循环冷却</p>
|
||||
<p class="author-badge">由 <strong>SCU 团队</strong> 拥有 · 编写者 <strong>S.A. [@SNOWARE]</strong></p>
|
||||
</header>
|
||||
|
||||
<!-- Features -->
|
||||
<div class="features">
|
||||
<div class="feature-card">
|
||||
<h3>🔍 高频检测</h3>
|
||||
<p>每 1.5 秒对 <code>127.0.0.1:25565</code> 发起 TCP 连接,毫秒级感知服务器状态变化。</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🧠 智能判断</h3>
|
||||
<p>严格区分端口超时与端口关闭——超时仅记录日志(避免网络抖动误判),关闭才触发重启。</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>⏳ 冷却期保护</h3>
|
||||
<p>重启后等待 200 秒,初次启动有 300 秒宽限期,彻底防止 MC 启动缓慢导致循环重启。Watchdog 同步适配(210s)。</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🛡️ 重试上限</h3>
|
||||
<p>连续 10 次重启失败后进入空闲等待模式,每 10 秒检测端口,恢复后自动退出。</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>🌍 跨平台</h3>
|
||||
<p>核心逻辑纯 Ruby 实现,支持 systemd / Docker / screen / Windows,轻松适配任意进程管理器。</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<h3>📝 日志完备</h3>
|
||||
<p>四级日志(debug / info / warn / error),输出到 systemd journal,排查问题一目了然。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Preview -->
|
||||
<div class="log-preview-section">
|
||||
<h2>📋 日志示例</h2>
|
||||
<p class="log-desc">通过 <code>journalctl -u minesentinel -f</code> 实时查看,以下为一次完整崩溃恢复过程的日志输出:</p>
|
||||
<div class="log-terminal">
|
||||
<div class="log-line"><span class="log-time">18:16:17</span><span class="log-level debug">DEBUG</span> self_diag: TCP check OK — 0.0002s</div>
|
||||
<div class="log-line"><span class="log-time">18:16:17</span><span class="log-level debug">DEBUG</span> mainloop: Server health check passed.</div>
|
||||
<div class="log-line"><span class="log-time">18:16:18</span><span class="log-level debug">DEBUG</span> self_diag: TCP check OK — 0.0003s</div>
|
||||
<div class="log-line"><span class="log-time">18:16:18</span><span class="log-level debug">DEBUG</span> mainloop: Server health check passed.</div>
|
||||
<div class="log-line"><span class="log-time">18:16:20</span><span class="log-level warn">WARN</span> self_diag: TCP port closed — server is down.</div>
|
||||
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level warn">WARN</span> mainloop: Server health check FAILED: Server died for unknown reason, and will be restarted.</div>
|
||||
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level warn">WARN</span> mainloop: Action: Record → Server died!</div>
|
||||
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level info">INFO</span> mainloop: Action: ForceResetServerProcess triggered.</div>
|
||||
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level info">INFO</span> reset: Attempt #1 to reset Minecraft server.</div>
|
||||
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level info">INFO</span> reset: Spawning 'systemctl restart minecraft' (PID will be captured)...</div>
|
||||
<div class="log-line"><span class="log-time">18:16:21</span><span class="log-level debug">DEBUG</span> reset: systemctl restart spawned with new PID.</div>
|
||||
<div class="log-line"><span class="log-time">18:16:51</span><span class="log-level warn">WARN</span> reset: 'systemctl restart' timed out after 30s. Force-killing process...</div>
|
||||
<div class="log-line"><span class="log-time">18:16:51</span><span class="log-level warn">WARN</span> reset: Running 'systemctl kill minecraft' (last resort)...</div>
|
||||
<div class="log-line"><span class="log-time">18:16:52</span><span class="log-level info">INFO</span> reset: Running 'systemctl start minecraft' (rebirth)...</div>
|
||||
<div class="log-line"><span class="log-time">18:16:52</span><span class="log-level info">INFO</span> reset: Force-reset sequence completed.</div>
|
||||
<div class="log-line"><span class="log-time">18:16:52</span><span class="log-level info">INFO</span> mainloop: Cooling down 200s for server startup...</div>
|
||||
<div class="log-line" style="opacity:.5;"><span class="log-time">... ... ...</span></div>
|
||||
<div class="log-line"><span class="log-time">18:20:12</span><span class="log-level info">INFO</span> mainloop: Cooldown finished, resuming monitoring.</div>
|
||||
<div class="log-line"><span class="log-time">18:20:14</span><span class="log-level debug">DEBUG</span> self_diag: TCP check OK — 0.0002s</div>
|
||||
<div class="log-line"><span class="log-time">18:20:15</span><span class="log-level debug">DEBUG</span> mainloop: Server health check passed.</div>
|
||||
</div>
|
||||
<p class="log-note" style="margin-top:14px;color:var(--text-dim);font-size:0.88em;">💡 日志中 PID、精确时间戳等敏感信息已脱敏。实际输出格式与 <code>journald</code> 一致,支持 <code>--since</code>、<code>-f</code> 等标准过滤参数。</p>
|
||||
</div>
|
||||
|
||||
<!-- Deploy Tutorial -->
|
||||
<div class="deploy-section">
|
||||
<h2>📦 部署教程</h2>
|
||||
|
||||
<h3>1. 环境要求</h3>
|
||||
<table>
|
||||
<tr><th>项目</th><th>说明</th></tr>
|
||||
<tr><td>操作系统</td><td>Linux(需 systemd),也支持 Windows</td></tr>
|
||||
<tr><td>Ruby</td><td>>= 3.0</td></tr>
|
||||
<tr><td>权限</td><td>root(systemctl 需要)</td></tr>
|
||||
<tr><td>MC 服务</td><td>已注册为 systemd 服务,默认名 <code>minecraft</code></td></tr>
|
||||
</table>
|
||||
|
||||
<h3>2. 获取源码</h3>
|
||||
<p style="margin-bottom:6px;">方式一:Git 克隆(推荐,仅拉取必要文件,排除仓库冗余)</p>
|
||||
<pre>git clone --depth 1 --single-branch \
|
||||
https://gitee.com/SCU_team/mine-sentinel.git \
|
||||
/opt/minecraft/sentinel
|
||||
# --depth 1 仅拉取最新提交,不下载历史记录
|
||||
# --single-branch 仅拉取 master 分支</pre>
|
||||
<p style="margin-bottom:6px;">方式二:手动复制</p>
|
||||
<pre>mkdir -p /opt/minecraft/sentinel
|
||||
cp main.rb method_executor.rb server_status_detector.rb \
|
||||
/opt/minecraft/sentinel/</pre>
|
||||
|
||||
<h3>3. 手动测试</h3>
|
||||
<pre>cd /opt/minecraft/sentinel
|
||||
ruby main.rb
|
||||
# 调试模式: LOG_LEVEL=debug ruby main.rb</pre>
|
||||
|
||||
<h3>4. 注册 systemd 服务</h3>
|
||||
<p>项目源码中已提供 <code>minesentinel.service</code>,直接复制:</p>
|
||||
<pre>cp minesentinel.service /etc/systemd/system/</pre>
|
||||
<p>单元文件内容:</p>
|
||||
<pre>[Unit]
|
||||
Description=MineSentinel Minecraft Server Monitor
|
||||
After=network.target
|
||||
Before=minecraft.service
|
||||
Wants=minecraft.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/minecraft/sentinel
|
||||
ExecStart=/usr/bin/ruby /opt/minecraft/sentinel/main.rb
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
#Environment=LOG_LEVEL=debug
|
||||
WatchdogSec=210
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target</pre>
|
||||
|
||||
<h4>🔗 启动顺序</h4>
|
||||
<p style="margin-bottom:10px;">MineSentinel 通过 <code>Before=minecraft.service</code> 确保先于 MC 启动,消除守护盲区。首次启动有 <strong>300 秒宽限期</strong>等待端口就绪:</p>
|
||||
<div class="flow-box">开机 → network.target
|
||||
│
|
||||
├─ MineSentinel 启动(最先)
|
||||
│ └─ 进入 300s 宽限期,等待 MC 端口就绪
|
||||
│
|
||||
└─ minecraft 随后启动(Before=)
|
||||
└─ 端口打开 → MineSentinel 恢复正常监控</div>
|
||||
|
||||
<pre>systemctl daemon-reload
|
||||
systemctl enable minesentinel # 同时触发 minecraft(Wants=)
|
||||
systemctl start minesentinel</pre>
|
||||
|
||||
<h3>5. 查看日志</h3>
|
||||
<pre>journalctl -u minesentinel -f # 实时
|
||||
journalctl -u minesentinel -u minecraft -f # 双服务合并</pre>
|
||||
|
||||
<h3>⚙️ 可调参数</h3>
|
||||
<table>
|
||||
<tr><th>参数</th><th>文件</th><th>行</th><th>默认</th></tr>
|
||||
<tr><td>初始宽限期</td><td>main.rb</td><td>31</td><td>300 s</td></tr>
|
||||
<tr><td>检测间隔</td><td>main.rb</td><td>69</td><td>1.5 s</td></tr>
|
||||
<tr><td>冷却期</td><td>main.rb</td><td>58</td><td>200 s</td></tr>
|
||||
<tr><td>systemctl 超时</td><td>method_executor.rb</td><td>17</td><td>30 s</td></tr>
|
||||
<tr><td>最大重试</td><td>method_executor.rb</td><td>23</td><td>10 次</td></tr>
|
||||
<tr><td>检测端口</td><td>server_status_detector.rb</td><td>60</td><td>25565</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>🔄 工作流程</h3>
|
||||
<div class="flow-box">MineSentinel 启动:
|
||||
└─ 300s 宽限期 → 等待 MC 端口就绪(避免误判)
|
||||
|
||||
每 1.5 秒循环:
|
||||
├─ TCP 连接 127.0.0.1:25565
|
||||
├─ 端口开放 → 继续循环
|
||||
├─ 端口超时 → 仅记录日志(不重启)
|
||||
└─ 端口关闭 → 触发重启 → 冷却 200 秒
|
||||
└─ 连续 10 次失败 → 空闲等待</div>
|
||||
|
||||
<h3>🪟 Windows / Docker / 其他场景</h3>
|
||||
<ul>
|
||||
<li><strong>Docker:</strong>替换重启命令为 <code>spawn "docker restart mc-server"</code></li>
|
||||
<li><strong>screen:</strong><code>system "screen -S mc -X quit"; system "screen -dmS mc java ..."</code></li>
|
||||
<li><strong>Windows:</strong>删除 root 检查,替换为 <code>spawn "cmd /c start /B java ..."</code>,日志输出到文件</li>
|
||||
<li><strong>监控其他服务:</strong>修改 <code>test_port</code> 的 IP 和端口即可</li>
|
||||
</ul>
|
||||
<p style="margin-top: 12px; color: var(--text-dim);">详细适配指南见 <a href="https://gitee.com/SCU_team/mine-sentinel/blob/master/DEPLOY.md" style="color: var(--orange);">DEPLOY.md</a></p>
|
||||
</div>
|
||||
|
||||
<!-- Gitee Widget -->
|
||||
<div class="widget-container">
|
||||
<div id="osc-gitee-widget-tag"></div>
|
||||
</div>
|
||||
|
||||
<!-- CTA -->
|
||||
<div class="cta-section">
|
||||
<button onclick="openModal()" class="btn btn-primary">⬇️ 获取 MineSentinel</button>
|
||||
<a href="https://gitee.com/SCU_team/mine-sentinel" class="btn">⭐ Gitee 仓库</a>
|
||||
<a href="https://gitee.com/SCU_team/mine-sentinel/blob/master/DEPLOY.md" class="btn">📖 完整文档</a>
|
||||
</div>
|
||||
|
||||
<!-- Download Modal -->
|
||||
<div id="dlModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="modal-close" onclick="closeModal()">×</span>
|
||||
<h2 class="modal-title">⬇️ 获取 MineSentinel</h2>
|
||||
<div class="modal-body">
|
||||
<div class="download-info">
|
||||
<p><strong>Git 克隆:</strong></p>
|
||||
<pre style="margin:8px 0;font-size:0.85rem;">git clone https://gitee.com/SCU_team/mine-sentinel.git</pre>
|
||||
<p style="margin-top:10px;"><strong>直接下载:</strong></p>
|
||||
<p><a href="https://gitee.com/SCU_team/mine-sentinel/repository/archive/master.zip" target="_blank" class="download-link">📦 下载 ZIP 压缩包</a></p>
|
||||
<div class="password-info" style="margin-top: 12px;">
|
||||
<strong>📋 要求:</strong> Ruby >= 3.0 · Linux (systemd) 或 Windows
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 16px; color: var(--text-dim); font-size: 0.9em;">💡 部署前请阅读 <a href="https://gitee.com/SCU_team/mine-sentinel/blob/master/DEPLOY.md" style="color: var(--orange);">DEPLOY.md</a>。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>© 2026 MineSentinel — S.A. SNOWARE-SCU</p>
|
||||
<p>源码采用 <a href="http://license.coscl.org.cn/MulanPubL-2.0" style="color:#fcd34d;">MulanPubL v2.0</a> · 品牌标识(Logo/SVG)保留所有权利</p>
|
||||
<div class="links">
|
||||
<a href="https://gitee.com/SCU_team/mine-sentinel">Gitee</a>
|
||||
<a href="https://gitee.com/SCU_team/mine-sentinel/issues">问题反馈</a>
|
||||
<a href="https://gitee.com/SCU_team/mine-sentinel/blob/master/DEPLOY.md">部署文档</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openModal() {
|
||||
document.getElementById('dlModal').classList.add('show');
|
||||
}
|
||||
function closeModal() {
|
||||
document.getElementById('dlModal').classList.remove('show');
|
||||
}
|
||||
window.onclick = function(e) {
|
||||
if (e.target === document.getElementById('dlModal')) closeModal();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,73 @@
|
||||
# MineSentinel
|
||||
|
||||
```
|
||||
🛡️ MineSentinel
|
||||
Minecraft 服务器守护进程
|
||||
```
|
||||
|
||||
**TCP 端口检测 · 崩溃自动重启 · 冷却期防循环**
|
||||
|
||||
[](http://license.coscl.org.cn/MulanPubL-2.0)
|
||||
[](https://www.ruby-lang.org)
|
||||
[](#跨场景适配指南)
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 复制文件
|
||||
mkdir -p /opt/minecraft/sentinel
|
||||
cp main.rb method_executor.rb server_status_detector.rb /opt/minecraft/sentinel/
|
||||
|
||||
# 安装 systemd 服务
|
||||
cp minesentinel.service /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
systemctl enable minesentinel
|
||||
systemctl start minesentinel
|
||||
|
||||
# 手动运行(调试用)
|
||||
cd /opt/minecraft/sentinel
|
||||
ruby main.rb
|
||||
```
|
||||
|
||||
## 特性
|
||||
|
||||
- **高频检测** — 每 1.5 秒 TCP 连接 `127.0.0.1:25565`
|
||||
- **智能判断** — 端口超时仅记录日志,端口关闭才触发重启
|
||||
- **冷却期保护** — 重启后等待 200 秒,防止 MC 启动缓慢导致循环重启
|
||||
- **初始宽限期** — MineSentinel 先于 minecraft 启动,自动等待 300 秒至端口就绪
|
||||
- **重试上限** — 连续 10 次失败进入空闲等待,端口恢复后自动退出
|
||||
- **多场景适配** — 支持 systemd / Docker / screen / Windows
|
||||
|
||||
## 服务启动顺序
|
||||
|
||||
```
|
||||
开机 → network.target
|
||||
│
|
||||
┌────▼────────┐
|
||||
│ MineSentinel │ 最先启动,进入 300s 宽限期等待 MC 就绪
|
||||
└────┬────────┘
|
||||
│ Before=
|
||||
┌────▼────┐
|
||||
│minecraft │ 随后启动,TCP 端口打开后 MineSentinel 恢复正常监控
|
||||
└─────────┘
|
||||
```
|
||||
|
||||
MineSentinel 通过 `Before=minecraft.service` 确保先于 MC 启动,避免 MC 先启动后崩溃而未被守护的第一时间窗口。首次启动的 300 秒宽限期确保不会把 MC 的正常启动过程误判为崩溃。
|
||||
|
||||
## 文件说明
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `main.rb` | 主循环、冷却期控制、初始宽限期 |
|
||||
| `method_executor.rb` | 日志系统、systemctl 重启、计数器 |
|
||||
| `server_status_detector.rb` | TCP 端口检测模块 |
|
||||
| `minesentinel.service` | systemd 单元文件(独立源码) |
|
||||
|
||||
## 完整文档
|
||||
|
||||
参见 [DEPLOY.md](DEPLOY.md) 或 [官网](OfficialWebsite/index.html)。
|
||||
|
||||
## 许可证
|
||||
|
||||
[MulanPubL v2.0](http://license.coscl.org.cn/MulanPubL-2.0) — Copyright © 2026 S.A. SNOWARE-SCU
|
||||
> 品牌标识(Logo / 项目名称)保留所有权利,未经许可不得用于衍生项目。
|
||||
@@ -2,7 +2,15 @@
|
||||
# usage: MINESENTINEL mainloop.
|
||||
# author: S.A.
|
||||
# time: 2026-06-07
|
||||
# license: All rights received.
|
||||
# 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.
|
||||
|
||||
# main.rb 顶部添加
|
||||
begin
|
||||
@@ -19,6 +27,22 @@ require_relative 'server_status_detector'
|
||||
|
||||
MethodExecutor.record("info", "mainloop", "MineSentinel started, entering mainloop.")
|
||||
|
||||
# 初次启动:MineSentinel 先于 minecraft 启动(systemd Before=),
|
||||
# 等待服务器端口就绪,避免误判为崩溃
|
||||
INITIAL_GRACE = 300 # 初次启动等待 300 秒
|
||||
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
|
||||
end
|
||||
MethodExecutor.record("info", "mainloop", "Grace period ended.")
|
||||
|
||||
# Functional mainloop.
|
||||
def main
|
||||
chkrslt = ServerStatusDetector::MinecraftServer.self_diag
|
||||
@@ -34,7 +58,7 @@ def main
|
||||
MethodExecutor.record("info", "mainloop", "Action: ForceResetServerProcess triggered.")
|
||||
MethodExecutor.reset_minecraft_server
|
||||
# 重启后等待服务器启动(MC服务器启动通常需要30秒~2分钟)
|
||||
cooldown = 190 # 190秒冷却期
|
||||
cooldown = 200 # 200秒冷却期
|
||||
MethodExecutor.record("info", "mainloop", "Cooling down #{cooldown}s for server startup...")
|
||||
sleep cooldown
|
||||
MethodExecutor.record("info", "mainloop", "Cooldown finished, resuming monitoring.")
|
||||
|
||||
+9
-1
@@ -2,7 +2,15 @@
|
||||
# usage: Automatic operator implementation.
|
||||
# author: S.A.
|
||||
# time: 2026-06-07
|
||||
# license: All rights received.
|
||||
# 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
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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.
|
||||
|
||||
[Unit]
|
||||
Description=MineSentinel Minecraft Server Monitor
|
||||
After=network.target
|
||||
Before=minecraft.service
|
||||
Wants=minecraft.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/minecraft/sentinel
|
||||
ExecStart=/usr/bin/ruby /opt/minecraft/sentinel/main.rb
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
#Environment=LOG_LEVEL=debug
|
||||
#Environment=RUBY_DEBUG=1
|
||||
WatchdogSec=210
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -2,7 +2,15 @@
|
||||
# usage: MINECRAFT Server monitoring.
|
||||
# author: S.A.
|
||||
# time: 2026-06-07
|
||||
# license: All rights received.
|
||||
# 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
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# <<*>> -^v- SNOWARE APPLICATION
|
||||
# usage: UNIVERSAL
|
||||
# author: S.A.
|
||||
# time: 2026-06-07
|
||||
# license: All rights received.
|
||||
|
||||
# CRITICAL! This function decides where the log file will be stored.
|
||||
def generate_logfile_path(
|
||||
proc_duty = "MineSentinel",
|
||||
time_format = "%Y%m%d_%H%M"
|
||||
)
|
||||
|
||||
# Ensure that destination exists.
|
||||
result = system "mkdir -p /var/log/snoware"
|
||||
if not result
|
||||
raise Errno::EBADEXEC
|
||||
end
|
||||
|
||||
return "/var/log/snoware" + "/" + proc_duty + "/" + Time.now.strftime(time_format) + ".log"
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user