试运营版本

This commit is contained in:
张梦南 2026-05-27 19:03:20 +08:00
parent b1706b606a
commit 1384945a24
6 changed files with 1107 additions and 0 deletions

264
DreamLife_easytier/app.js Normal file
View File

@ -0,0 +1,264 @@
class SystemMonitor {
constructor() {
this.cpuValueEl = document.getElementById('cpu-value');
this.cpuBarEl = document.getElementById('cpu-bar');
this.memoryValueEl = document.getElementById('memory-value');
this.memoryBarEl = document.getElementById('memory-bar');
this.serverStatusEl = document.getElementById('server-status');
this.lastUpdateEl = document.getElementById('last-update');
this.updateInterval = 1000;
this.isOnline = false;
this.cpuApiUrl = 'https://dreamlife.indevs.in:61207/api/4/cpu';
this.memoryApiUrl = 'https://dreamlife.indevs.in:61207/api/4/mem';
this.init();
}
init() {
this.fetchSystemInfo();
setInterval(() => this.fetchSystemInfo(), this.updateInterval);
}
async fetchSystemInfo() {
try {
const [cpuResponse, memoryResponse] = await Promise.all([
fetch(this.cpuApiUrl, {
method: 'GET',
headers: {
'Accept': 'application/json'
},
signal: AbortSignal.timeout(5000)
}),
fetch(this.memoryApiUrl, {
method: 'GET',
headers: {
'Accept': 'application/json'
},
signal: AbortSignal.timeout(5000)
})
]);
if (!cpuResponse.ok || !memoryResponse.ok) {
throw new Error('API 请求失败');
}
const cpuData = await cpuResponse.json();
const memoryData = await memoryResponse.json();
this.updateUI(cpuData, memoryData);
this.setOnlineStatus(true);
} catch (error) {
console.error('获取系统信息失败:', error);
this.setOnlineStatus(false);
this.showMockData();
}
}
updateUI(cpuData, memoryData) {
// Glances CPU API:
// { "total": 12.3, ... }
const cpuPercent = Number(cpuData.total || 0).toFixed(2);
// Glances MEM API:
// { "percent": 45.6, ... }
const memoryPercent = Number(memoryData.percent || 0).toFixed(2);
this.cpuValueEl.textContent = `${cpuPercent}%`;
this.cpuBarEl.style.width = `${Math.round(cpuPercent)}%`;
this.memoryValueEl.textContent = `${memoryPercent}%`;
this.memoryBarEl.style.width = `${Math.round(memoryPercent)}%`;
const now = new Date();
const timeString = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
});
this.lastUpdateEl.textContent = timeString;
}
setOnlineStatus(online) {
if (this.isOnline !== online) {
this.isOnline = online;
if (online) {
this.serverStatusEl.textContent = '在线';
this.serverStatusEl.className = 'info-value status-online';
} else {
this.serverStatusEl.textContent = '离线';
this.serverStatusEl.className = 'info-value status-offline';
}
}
}
showMockData() {
const mockCpu = Math.floor(Math.random() * 40) + 20;
const mockMemory = Math.floor(Math.random() * 30) + 40;
this.cpuValueEl.textContent = `${mockCpu}%`;
this.cpuBarEl.style.width = `${mockCpu}%`;
this.memoryValueEl.textContent = `${mockMemory}%`;
this.memoryBarEl.style.width = `${mockMemory}%`;
const now = new Date();
this.lastUpdateEl.textContent = now.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
});
}
}
let isProtocolMenuOpen = false;
function toggleProtocolMenu() {
const selector = document.getElementById('protocol-selector');
isProtocolMenuOpen = !isProtocolMenuOpen;
if (isProtocolMenuOpen) {
selector.classList.add('open');
} else {
selector.classList.remove('open');
}
}
function selectProtocol(protocol) {
const ports = {
'tcp': 11010,
'udp': 11010,
'wg': 11011,
'ws':11011,
'wss':11012,
'quic':11012,
'faketcp':11013,
'http':80,
'https':443,
'txt':null,
'srv':null
};
const protocolNames = {
'tcp': 'TCP',
'udp': 'UDP',
'wg': 'WG',
'ws': 'WS',
'wss': 'WSS',
'quic': 'QUIC',
'faketcp': 'FakeTCP',
'http': 'HTTP',
'https': 'HTTPS',
'txt': 'TXT',
'srv': 'SRV'
};
document.getElementById('current-protocol').textContent = protocolNames[protocol];
const addressCode = document.getElementById('node-address');
if (ports[protocol] === null) {
addressCode.firstChild.textContent = `${protocol}://dreamlife.indevs.in\n`;
} else {
addressCode.firstChild.textContent = `${protocol}://dreamlife.indevs.in:${ports[protocol]}\n`;
}
document.querySelectorAll('.protocol-option').forEach(opt => {
opt.classList.remove('active');
});
document.querySelector(`.protocol-option[data-protocol="${protocol}"]`).classList.add('active');
toggleProtocolMenu();
}
function copyAddress() {
const address = document.getElementById('node-address').textContent;
navigator.clipboard.writeText(address).then(() => {
const copyIcon = document.getElementById('copy-icon');
const checkIcon = document.getElementById('check-icon');
copyIcon.style.display = 'none';
checkIcon.style.display = 'block';
setTimeout(() => {
copyIcon.style.display = 'block';
checkIcon.style.display = 'none';
}, 2000);
}).catch(err => {
console.error('复制失败:', err);
alert('复制失败,请手动复制');
});
}
document.addEventListener('click', function(event) {
const selector = document.getElementById('protocol-selector');
if (!selector.contains(event.target) && isProtocolMenuOpen) {
selector.classList.remove('open');
isProtocolMenuOpen = false;
}
});
async function testLatency() {
const latencyText = document.querySelector('.latency-text');
const testBtn = document.getElementById('latency-test-btn');
testBtn.disabled = true;
testBtn.textContent = '测试中...';
latencyText.textContent = '--';
latencyText.className = 'latency-text';
try {
const startTime = performance.now();
await fetch('https://dreamlife.indevs.in:61207/api/4/cpu', {
method: 'GET',
signal: AbortSignal.timeout(5000)
});
const endTime = performance.now();
const latency = Math.round(endTime - startTime);
latencyText.textContent = `${latency}ms`;
if (latency < 100) {
latencyText.classList.add('latency-good');
} else if (latency < 300) {
latencyText.classList.add('latency-medium');
} else {
latencyText.classList.add('latency-bad');
}
testBtn.textContent = '重测';
} catch (error) {
console.error('延迟测试失败:', error);
latencyText.textContent = '超时';
latencyText.classList.add('latency-bad');
testBtn.textContent = '重测';
} finally {
testBtn.disabled = false;
}
}
document.addEventListener('DOMContentLoaded', () => {
new SystemMonitor();
setTimeout(() => {
testLatency();
}, 500);
});

BIN
DreamLife_easytier/dl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View File

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DreamLife - EasyTier公共节点</title>
<link rel="icon" href="dl.png" type="image/png">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header class="header">
<div class="logo-container">
<img src="dl.png" alt="DreamLife Logo" class="icon-logo">
<img src="dreamlife.png" alt="DreamLife Text" class="text-logo">
</div>
<p class="subtitle">EasyTier 公共节点服务</p>
</header>
<main class="main-content">
<section class="connection-section">
<h2>连接信息</h2>
<div class="connection-card">
<p class="connection-tip">使用 EasyTier 客户端初始节点配置:</p>
<div class="connection-details">
<div class="detail-item">
<span class="detail-label">节点地址:</span>
</div>
<div class="address-center">
<div class="protocol-selector" id="protocol-selector">
<button class="protocol-toggle" id="protocol-toggle" onclick="toggleProtocolMenu()">
<span id="current-protocol">TCP</span>
<svg class="chevron" id="chevron" viewBox="0 0 16 16" width="14" height="14">
<path d="M4.427 5.427a.75.75 0 0 1 1.06 0L8 8.023l2.513-2.596a.75.75 0 1 1 1.003 1.087l-3 3.099a.75.75 0 0 1-1.003 0l-3-3.099a.75.75 0 0 1 0-1.087Z"/>
</svg>
</button>
<div class="protocol-menu" id="protocol-menu">
<div class="protocol-option active" data-protocol="tcp" onclick="selectProtocol('tcp')">TCP</div>
<div class="protocol-option" data-protocol="udp" onclick="selectProtocol('udp')">UDP</div>
<div class="protocol-option" data-protocol="wg" onclick="selectProtocol('wg')">WG</div>
<div class="protocol-option" data-protocol="ws" onclick="selectProtocol('ws')">WS</div>
<div class="protocol-option" data-protocol="wss" onclick="selectProtocol('wss')">WSS</div>
<div class="protocol-option" data-protocol="quic" onclick="selectProtocol('quic')">QUIC</div>
<div class="protocol-option" data-protocol="faketcp" onclick="selectProtocol('faketcp')">FakeTCP</div>
<div class="protocol-option" data-protocol="http" onclick="selectProtocol('http')">HTTP</div>
<div class="protocol-option" data-protocol="https" onclick="selectProtocol('https')">HTTPS</div>
<div class="protocol-option" data-protocol="txt" onclick="selectProtocol('txt')">TXT</div>
<div class="protocol-option" data-protocol="srv" onclick="selectProtocol('srv')">SRV</div>
</div>
</div>
<code class="detail-value" id="node-address">tcp://dreamlife.indevs.in:11010
<button class="copy-button" id="copy-button" onclick="copyAddress()" aria-label="复制代码">
<svg class="copy-icon" id="copy-icon" viewBox="0 0 16 16" width="14" height="14">
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/>
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5a.75.75 0 0 1 0-1.5h1.5a.25.25 0 0 0 .25-.25v-7.5Z"/>
</svg>
<svg class="check-icon" id="check-icon" viewBox="0 0 16 16" width="14" height="14" style="display: none;">
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/>
</svg>
</button>
</code>
</div>
</div>
</div>
</section>
<section class="status-section">
<h2>公共服务器状态</h2>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-header">
<span class="metric-label">CPU 占用率</span>
<span class="metric-value" id="cpu-value">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="cpu-bar" style="width: 0%"></div>
</div>
</div>
<div class="metric-card">
<div class="metric-header">
<span class="metric-label">内存占用率</span>
<span class="metric-value" id="memory-value">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="memory-bar" style="width: 0%"></div>
</div>
</div>
<div class="metric-card">
<div class="metric-header">
<span class="metric-label">网络延迟</span>
<span class="metric-value" id="latency-value">
<span class="latency-text">--</span>
<button class="latency-test-btn" id="latency-test-btn" onclick="testLatency()">测试</button>
</span>
</div>
</div>
<div class="metric-card">
<div class="metric-header">
<span class="metric-label">服务器位置</span>
</div>
<div class="location-box">
<svg class="location-icon" viewBox="0 0 16 16" width="16" height="16">
<path d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10Zm0-7a3 3 0 1 1 0-6 3 3 0 0 1 0 6Z"/>
</svg>
<span class="location-text">华东-上海</span>
</div>
</div>
</div>
<div class="server-info">
<div class="info-row">
<span class="info-label">节点状态:</span>
<span class="info-value status-online" id="server-status">在线</span>
</div>
<div class="info-row">
<span class="info-label">最后更新:</span>
<span class="info-value" id="last-update">--</span>
</div>
</div>
</section>
</main>
<footer class="footer">
<p>&copy; 2026 由 DreamLife 提供公益支持</p>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>

View File

@ -0,0 +1,480 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #3b82f6;
--bg-color: #f8fafc;
--card-bg: #ffffff;
--text-primary: #1e293b;
--text-secondary: #64748b;
--border-color: #e2e8f0;
--success-color: #10b981;
--hover-color: #2563eb;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
background-color: var(--bg-color);
color: var(--text-primary);
line-height: 1.6;
}
.container {
max-width: 900px;
margin: 0 auto;
padding: 40px 20px;
}
.header {
text-align: center;
margin-bottom: 60px;
}
.logo-container {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-bottom: 16px;
}
.icon-logo {
width: 100px;
height: auto;
}
.text-logo {
height: 60px;
width: auto;
}
.subtitle {
font-size: 18px;
color: var(--text-secondary);
font-weight: 300;
}
.main-content {
display: flex;
flex-direction: column;
gap: 40px;
}
h2 {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: var(--text-primary);
}
.status-section {
background: var(--card-bg);
border-radius: 16px;
padding: 32px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
margin-bottom: 24px;
}
.metric-card {
padding: 20px;
background: var(--bg-color);
border-radius: 12px;
border: 1px solid var(--border-color);
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 120px;
}
.metric-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.metric-label {
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
}
.metric-value {
font-size: 24px;
font-weight: 700;
color: var(--primary-color);
}
.progress-bar {
height: 8px;
background: var(--border-color);
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--hover-color));
border-radius: 4px;
transition: width 0.5s ease;
}
.server-info {
padding: 20px;
background: var(--bg-color);
border-radius: 12px;
border: 1px solid var(--border-color);
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
}
.info-row:not(:last-child) {
border-bottom: 1px solid var(--border-color);
}
.info-label {
font-size: 14px;
color: var(--text-secondary);
}
.info-value {
font-size: 14px;
font-weight: 500;
}
.status-online {
color: var(--success-color);
}
.status-offline {
color: #ef4444;
}
.connection-section {
background: var(--card-bg);
border-radius: 16px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.connection-card {
padding: 16px;
background: var(--bg-color);
border-radius: 12px;
border: 1px solid var(--border-color);
}
.connection-tip {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 12px;
}
.connection-details {
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-item {
display: flex;
align-items: center;
gap: 12px;
}
.detail-label {
font-size: 14px;
color: var(--text-secondary);
min-width: 80px;
}
.detail-value {
position: relative;
font-family: 'Courier New', monospace;
font-size: 14px;
font-weight: 600;
background: var(--card-bg);
padding: 8px 16px;
padding-right: 40px;
border-radius: 8px;
border: 1px solid var(--border-color);
color: #000000;
display: inline-block;
max-width: 100%;
}
.protocol-selector {
position: relative;
display: inline-block;
}
.protocol-toggle {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
font-size: 13px;
font-weight: 500;
background: rgba(175, 184, 193, 0.2);
color: #57606a;
border: 1px solid rgba(31, 35, 40, 0.15);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.protocol-toggle:hover {
background: rgba(175, 184, 193, 0.4);
color: #24292f;
}
.protocol-toggle .chevron {
transition: transform 0.2s ease;
fill: currentColor;
}
.protocol-selector.open .chevron {
transform: rotate(180deg);
}
.protocol-menu {
position: absolute;
top: 100%;
left: 0;
margin-top: 4px;
background: var(--card-bg);
border: 1px solid rgba(31, 35, 40, 0.15);
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
overflow: hidden;
opacity: 0;
visibility: hidden;
transform: translateY(-10px);
transition: all 0.2s ease;
z-index: 100;
min-width: 100%;
}
.protocol-selector.open .protocol-menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.protocol-option {
padding: 8px 16px;
font-size: 13px;
color: #57606a;
cursor: pointer;
transition: all 0.15s ease;
white-space: nowrap;
}
.protocol-option:hover {
background: rgba(175, 184, 193, 0.2);
color: #24292f;
}
.protocol-option.active {
background: var(--primary-color);
color: white;
}
.address-center {
display: flex;
justify-content: center;
align-items: center;
margin-top: 4px;
flex-wrap: wrap;
gap: 12px;
}
.copy-button {
position: absolute;
top: 50%;
right: 8px;
transform: translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
font-size: 12px;
background: rgba(175, 184, 193, 0.2);
color: #57606a;
border: 1px solid rgba(31, 35, 40, 0.15);
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
min-width: 26px;
min-height: 26px;
}
.copy-button:hover {
background: rgba(175, 184, 193, 0.4);
color: #24292f;
}
.copy-button:active {
transform: translateY(-50%) scale(0.95);
}
.copy-icon,
.check-icon {
fill: currentColor;
flex-shrink: 0;
}
#latency-value {
display: flex;
align-items: center;
gap: 10px;
}
.latency-text {
font-size: 20px;
}
.location-box {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px;
background: var(--bg-color);
border-radius: 8px;
border: 1px solid var(--border-color);
}
.location-icon {
fill: var(--primary-color);
flex-shrink: 0;
}
.location-text {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.latency-test-btn {
padding: 4px 12px;
font-size: 12px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.latency-test-btn:hover {
background: var(--hover-color);
}
.latency-test-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.latency-good {
color: var(--success-color);
}
.latency-medium {
color: #f59e0b;
}
.latency-bad {
color: #ef4444;
}
.connection-section h2 {
margin-bottom: 16px;
}
.footer {
margin-top: 60px;
text-align: center;
padding: 20px;
color: var(--text-secondary);
font-size: 14px;
}
@media (max-width: 640px) {
.container {
padding: 20px 16px;
}
.logo-container {
flex-direction: column;
gap: 12px;
}
.icon-logo {
width: 80px;
}
.text-logo {
height: 60px;
}
.metrics-grid {
grid-template-columns: 1fr;
}
.status-section,
.connection-section {
padding: 24px;
}
.address-center {
flex-direction: column;
gap: 12px;
}
.detail-value {
width: 100%;
font-size: 13px;
word-break: break-all;
}
.protocol-selector {
width: 100%;
}
.protocol-toggle {
width: 100%;
justify-content: center;
}
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
.loading {
animation: pulse 1.5s ease-in-out infinite;
}

230
install-easytier-relay.sh Normal file
View File

@ -0,0 +1,230 @@
#!/usr/bin/env bash
set -e
echo "======================================"
echo " EasyTier 公益中继节点一键部署脚本"
echo "======================================"
if [ "$(id -u)" != "0" ]; then
echo "请使用 root 运行"
exit 1
fi
########################################
# 检测包管理器
########################################
if command -v dnf >/dev/null 2>&1; then
PM="dnf"
elif command -v yum >/dev/null 2>&1; then
PM="yum"
elif command -v apt >/dev/null 2>&1; then
PM="apt"
elif command -v zypper >/dev/null 2>&1; then
PM="zypper"
else
echo "不支持的 Linux 发行版"
exit 1
fi
echo "检测到包管理器: $PM"
########################################
# 安装基础组件
########################################
case $PM in
apt)
apt update
apt install -y curl wget sudo fail2ban
;;
dnf|yum)
$PM install -y epel-release || true
$PM install -y curl wget sudo fail2ban
;;
zypper)
zypper install -y curl wget sudo fail2ban
;;
esac
########################################
# 安装 EasyTier
########################################
echo "安装 EasyTier..."
wget -O /tmp/easytier.sh \
"https://raw.githubusercontent.com/EasyTier/EasyTier/main/script/install.sh"
bash /tmp/easytier.sh install --gh-proxy https://ghfast.top/
########################################
# 查找 easytier-core
########################################
EASYTIER_BIN=""
for p in \
/usr/local/bin/easytier-core \
/usr/bin/easytier-core \
/opt/easytier/easytier-core
do
if [ -f "$p" ]; then
EASYTIER_BIN="$p"
break
fi
done
if [ -z "$EASYTIER_BIN" ]; then
echo "未找到 easytier-core"
exit 1
fi
echo "EasyTier 路径: $EASYTIER_BIN"
########################################
# 主机名
########################################
HOSTNAME=$(hostname)
########################################
# 创建 systemd 服务
########################################
cat >/etc/systemd/system/easytier.service <<EOF
[Unit]
Description=EasyTier Service
After=network.target
[Service]
Type=simple
ExecStart=$EASYTIER_BIN \\
--hostname $HOSTNAME \\
--relay-network-whitelist "" \\
--relay-all-peer-rpc
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=300
StartLimitBurst=10
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
EOF
########################################
# sysctl 优化
########################################
cat >/etc/sysctl.d/99-easytier.conf <<EOF
net.core.somaxconn = 4096
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 4096
net.core.netdev_max_backlog = 4096
net.ipv4.udp_rmem_min = 8192
net.ipv4.udp_wmem_min = 8192
net.ipv4.ip_forward = 0
EOF
########################################
# BBR
########################################
cat >/etc/sysctl.d/99-bbr.conf <<EOF
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sysctl --system
########################################
# fail2ban
########################################
mkdir -p /etc/fail2ban/filter.d
cat >/etc/fail2ban/filter.d/easytier.conf <<EOF
[Definition]
failregex = remote: \\S+://<HOST>:\\d+, err: wait resp error:.+
EOF
cat >/etc/fail2ban/jail.local <<EOF
[easytier]
enabled = true
filter = easytier
backend = systemd
journalmatch = _SYSTEMD_UNIT=easytier.service
maxretry = 3
findtime = 600
bantime = 3600
banaction = firewallcmd-ipset
EOF
########################################
# 防火墙
########################################
if systemctl is-active firewalld >/dev/null 2>&1; then
firewall-cmd --permanent --add-port=11010/tcp
firewall-cmd --permanent --add-port=11010/udp
firewall-cmd --permanent --add-port=11011/tcp
firewall-cmd --permanent --add-port=11011/udp
firewall-cmd --permanent --add-port=11012/tcp
firewall-cmd --permanent --add-port=11012/udp
firewall-cmd --permanent --add-port=11013/tcp
firewall-cmd --reload
fi
########################################
# 启动服务
########################################
systemctl daemon-reload
systemctl enable --now easytier
systemctl enable --now fail2ban
########################################
# 输出状态
########################################
echo
echo "======================================"
echo " EasyTier 公益节点部署完成"
echo "======================================"
echo
systemctl --no-pager --full status easytier || true
echo
echo "--------------------------------------"
fail2ban-client status || true
echo
echo "--------------------------------------"
echo "BBR 状态:"
sysctl net.ipv4.tcp_congestion_control
echo
echo "监听端口:"
ss -lntup | grep easytier || true
echo
echo "部署完成"