Files
sentinel/OfficialWebsite/index.html
T

675 lines
28 KiB
HTML
Raw Normal View History

2026-06-07 19:35:17 +08:00
<!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">
2026-06-07 19:38:03 +08:00
<img class="logo-png" src="https://raw.giteeusercontent.com/SCU_team/mine-sentinel/raw/master/OfficialWebsite/swsec.png" alt="SW Security">
2026-06-07 19:35:17 +08:00
<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>rootsystemctl 需要)</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 # 同时触发 minecraftWants=
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()">&times;</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>&copy; 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>