update 2026-05-10 09:48:30

This commit is contained in:
action committed 2026-05-10 09:48:30 +08:00
1 parent 95ac96413e
commit 0b362674f1
6244 files changed
+2992248

No files matched your search

+144
View File
@@ -0,0 +1,144 @@
name: Build-OpenWrt-EasyTier
on:
#push:
workflow_dispatch:
inputs:
tag:
description: '请填写发布Releases的版本号(留空不发布)'
required: false
default: ''
text:
description: '请填写发布的说明'
required: false
default: ''
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
TAG: "${{ github.event.inputs.tag }}"
TZ: Asia/Shanghai
permissions:
contents: write
jobs:
build:
name: Build ${{ matrix.arch }}-${{ matrix.sdk }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch:
- aarch64_cortex-a53
- aarch64_cortex-a72
- aarch64_generic
- arm_arm1176jzf-s_vfp
- arm_arm926ej-s
- arm_cortex-a15_neon-vfpv4
- arm_cortex-a5_vfpv4
- arm_cortex-a7
- arm_cortex-a7_neon-vfpv4
- arm_cortex-a8_vfpv3
- arm_cortex-a9
- arm_cortex-a9_neon
- arm_cortex-a9_vfpv3-d16
- arm_fa526
#- arm_mpcore
- arm_xscale
- mips_24kc
#- mips_4kec
- mips_mips32
- mipsel_24kc
- mipsel_24kc_24kf
- mipsel_74kc
- mipsel_mips32
- x86_64
sdk:
- 22.03.7
- SNAPSHOT
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Cache OpenWrt SDK
uses: actions/cache@v5
with:
path: |
/opt/openwrt-sdk-${{ matrix.arch }}-${{ matrix.sdk }}
dl
key: sdk-${{ matrix.arch }}-${{ matrix.sdk }}
- name: Create i18n uci-defaults file
run: |
mkdir -p luci-app-easytier/root/etc/uci-defaults
cat > luci-app-easytier/root/etc/uci-defaults/luci-i18n-easytier-zh-cn << 'EOF'
#!/bin/sh
uci -q batch << EOI
set luci.languages.zh_cn='简体中文 (Simplified Chinese)'
commit luci
EOI
rm -f /tmp/luci-indexcache /tmp/luci-modulecache/*
exit 0
EOF
chmod +x luci-app-easytier/root/etc/uci-defaults/luci-i18n-easytier-zh-cn
- name: Building packages
uses: openwrt/gh-action-sdk@main
env:
ARCH: ${{ matrix.arch }}-${{ matrix.sdk }}
FEEDNAME: packages_ci
PACKAGES: easytier easytier-noweb luci-app-easytier
#NO_DEFAULT_FEEDS: 1
NO_REFRESH_CHECK: 1
NO_SHFMT_CHECK: 1
MAKEFLAGS: -j$(nproc)
V: s
- name: Upload artifacts
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.arch }}-${{ matrix.sdk }}
path: bin/packages/${{ matrix.arch }}/packages_ci/*
release:
name: Publish Release
runs-on: ubuntu-latest
needs: build
if: ${{ github.event.inputs.tag != '' }}
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare release info
run: |
sudo timedatectl set-timezone "Asia/Shanghai"
mkdir -p release
for dir in artifacts/*; do
name=$(basename "$dir")
zip -j "release/EasyTier-${{ env.TAG }}-${name}.zip" "$dir"/*.ipk "$dir"/*.apk 2>/dev/null || true
done
echo "build_time=$(TZ=UTC-8 date '+%Y年%m月%d日%H:%M:%S' | jq -sRr @uri)" >> $GITHUB_ENV
- name: Release
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
body: |
> ### ![](https://img.shields.io/badge/%E7%BC%96%E8%AF%91%E6%97%B6%E9%97%B4-${{ env.build_time }}-easytier?logo=github&labelColor=%E9%A1%BB)![](https://img.shields.io/github/downloads/${{ github.repository }}/${{ env.TAG }}/total?label=%E4%B8%8B%E8%BD%BD%E6%AC%A1%E6%95%B0&logo=github)
- 其中 **`22.03.7`** 里面是ipk安装包 **`SNAPSHOT`** 里面是apk安装包
- **`*.apk` 是OpenWrt新版的APK包** 对于apk包跳过证书命令
`apk add --allow-untrusted luci-app-easytier.apk`
${{ github.event.inputs.text }}
tag_name: ${{ env.TAG }}
files: release/*.zip
+396
View File
@@ -0,0 +1,396 @@
![luci-app-easytier](https://socialify.git.ci/EasyTier/luci-app-easytier/image?description=1&font=JetBrains+Mono&forks=1&issues=1&logo=https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F169161851%3Fs%3D200%26v%3D4&name=1&pulls=1&stargazers=1&theme=Auto)
# 开发指南
[English](DEVELOPMENT_EN.md) | 简体中文
本文档详细说明 luci-app-easytier 的开发规范和要求。
## 核心原则
**必须兼容 OpenWrt 18.06 ~ 26.x,使用 LuCI Lua1 架构**
## 技术栈限制
### 后端 (Lua)
#### ✅ 允许使用
- Lua 5.1 标准语法
- LuCI Lua1 API:
- `luci.controller`
- `luci.model.uci`
- `luci.http`
- `luci.sys`
- `nixio.fs`
- 标准库: `io`, `os`, `string`, `table`, `math`
#### ❌ 禁止使用
- Lua2 特有语法
- 新版 LuCI JS API
- 任何需要额外依赖的库
### 前端 (JavaScript)
#### ✅ 允许使用
- ES5 语法
- `var` 声明变量
- `function` 关键字
- `XMLHttpRequest`
- 原生 DOM 操作
- 原生事件处理
#### ❌ 禁止使用
```javascript
// 禁止 ES6+ 语法
let x = 1; // ❌
const y = 2; // ❌
var z = () => {}; // ❌ 箭头函数
fetch('/api'); // ❌
async/await // ❌
import/export // ❌
模板字符串 `${x}` // ❌
解构赋值 // ❌
class 语法 // ❌
// 禁止框架
Vue/React/Angular // ❌
jQuery // ❌
任何构建工具 // ❌
```
#### ✅ 正确示例
```javascript
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/status', true);
xhr.onload = function() {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
document.getElementById('status').textContent = data.status;
}
};
xhr.send();
```
## 架构设计
### 页面渲染流程
```
用户请求 → Lua Controller → 渲染 HTM 模板 → 返回 HTML
↓
初始数据注入到页面
↓
JavaScript 增强交互
↓
AJAX 调用 Lua API
↓
更新局部 DOM
```
### 文件组织
```
controller/easytier.lua
├── index() # 路由注册
├── act_status() # 状态API
├── upload_binary() # 上传API
└── ...
view/easytier/status.htm
├── Lua 模板代码 (<%...%>)
├── HTML 结构
├── CSS 样式 (<style>)
└── JavaScript (<script>)
```
## 国际化 (i18n)
### Lua 中使用
```lua
local i18n = require "luci.i18n"
local translate = i18n.translate
-- 翻译文本
local text = translate("Hello World")
```
### HTM 模板中使用
```html
<!-- 简短语法 -->
<h1><%:Hello World%></h1>
<!-- 完整语法 -->
<p><%=translate("Welcome to EasyTier")%></p>
```
### JavaScript 中使用
```javascript
// 方式1: Lua 注入翻译
var msg = '<%=translate("Upload successful")%>';
alert(msg);
// 方式2: API 返回翻译
xhr.onload = function() {
var response = JSON.parse(xhr.responseText);
alert(response.message); // 后端已翻译
};
```
### 翻译文件
```po
# po/zh_Hans/easytier.po
msgid "Hello World"
msgstr "你好世界"
msgid "Upload successful"
msgstr "上传成功"
```
## API 设计
### 路由注册
```lua
-- controller/easytier.lua
function index()
entry({"admin", "vpn", "easytier"}, firstchild(), _("EasyTier"), 46).dependent = true
entry({"admin", "vpn", "easytier", "status"}, cbi("easytier_status"), _("Status"), 1).leaf = true
entry({"admin", "vpn", "easytier", "api_status"}, call("act_status")).leaf = true
end
```
### API 实现
```lua
function act_status()
local uci = require "luci.model.uci".cursor()
luci.http.prepare_content("application/json")
luci.http.write_json({
success = true,
running = luci.sys.call("pgrep easytier-core >/dev/null") == 0,
version = uci:get_first("easytier", "easytier", "version")
})
end
```
### 前端调用
```javascript
function updateStatus() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/cgi-bin/luci/admin/vpn/easytier/api_status', true);
xhr.onload = function() {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
if (data.success) {
document.getElementById('status').textContent =
data.running ? 'Running' : 'Stopped';
}
}
};
xhr.send();
}
```
## UCI 配置
### 读取配置
```lua
local uci = require "luci.model.uci".cursor()
-- 读取单个值
local enabled = uci:get("easytier", "@easytier[0]", "enabled")
-- 读取第一个section
local network_name = uci:get_first("easytier", "easytier", "network_name")
-- 读取列表
local peers = uci:get("easytier", "@easytier[0]", "peers") or {}
```
### 保存配置
```lua
-- 设置值
uci:set("easytier", "@easytier[0]", "enabled", "1")
uci:set("easytier", "@easytier[0]", "network_name", "mynet")
-- 设置列表
uci:delete("easytier", "@easytier[0]", "peers")
for _, peer in ipairs(peers) do
uci:set_list("easytier", "@easytier[0]", "peers", peer)
end
-- 提交更改
uci:commit("easytier")
```
## 日志处理
### 后端实现
```lua
function get_log()
local log_file = "/tmp/easytier.log"
local log = ""
if luci.sys.call("[ -f '" .. log_file .. "' ]") == 0 then
-- 去除ANSI颜色代码
log = luci.sys.exec("sed 's/\\x1b\\[[0-9;]*m//g' " .. log_file)
end
luci.http.write(log)
end
```
### 前端渲染
```javascript
function loadLog() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/cgi-bin/luci/admin/vpn/easytier/get_log', true);
xhr.onload = function() {
if (xhr.status === 200) {
var lines = xhr.responseText.split('\n');
var html = '';
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var level = 'info';
if (line.indexOf('ERROR') !== -1) level = 'error';
else if (line.indexOf('WARN') !== -1) level = 'warn';
else if (line.indexOf('DEBUG') !== -1) level = 'debug';
html += '<div class="log-line log-' + level + '">' +
escapeHtml(line) + '</div>';
}
document.getElementById('log_content').innerHTML = html;
}
};
xhr.send();
}
function escapeHtml(text) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(text));
return div.innerHTML;
}
```
## UI 设计规范
### 响应式布局
```css
/* 桌面端 */
.container {
max-width: 1200px;
margin: 0 auto;
}
/* 移动端 */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.grid {
grid-template-columns: 1fr;
}
}
```
### 主题适配
```css
:root {
--bg-color: #ffffff;
--text-color: #333333;
--border-color: #e0e0e0;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #1e1e1e;
--text-color: #e0e0e0;
--border-color: #3e3e3e;
}
}
.card {
background: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
```
## 测试要求
### 兼容性测试
必须在以下环境测试:
- ✅ OpenWrt 18.06 (Lua1)
- ✅ OpenWrt 22.03 (Lua1/Lua2)
- ✅ OpenWrt SNAPSHOT (Lua2)
### 浏览器测试
- ✅ Chrome/Edge (现代浏览器)
- ✅ Firefox
- ✅ Safari (iOS)
- ✅ 移动端浏览器
### 功能测试
- [ ] 配置保存和读取
- [ ] 服务启动和停止
- [ ] 文件上传
- [ ] 日志显示
- [ ] 状态更新
- [ ] 国际化切换
## 常见问题
### Q: 为什么不使用 lua2 和 js ?
A: 无法兼容老旧版本openwrt的使用,js不适合低配置路由器。
### Q: 为什么不使用 let/const?
A: OpenWrt 18.06 的浏览器环境不支持 ES6,必须使用 ES5 语法。
### Q: 为什么不使用 Vue/React?
A: 这些框架需要构建工具,且体积大,不适合嵌入式环境。
### Q: 如何实现动态更新?
A: 使用 XMLHttpRequest + setInterval 轮询,或 WebSocket(需要额外支持)。
### Q: 如何调试?
A: 使用浏览器开发者工具,查看 Console 和 Network 标签。
## 代码审查清单
提交前请确认:
- [ ] 没有使用 ES6+ 语法
- [ ] 没有使用前端框架
- [ ] 所有文本都已国际化
- [ ] API 返回 JSON 格式
- [ ] 错误处理完善
- [ ] 代码有适当注释
- [ ] 在 OpenWrt 18.06 上测试通过
- [ ] 移动端显示正常
- [ ] 暗色主题显示正常
## 参考资源
- [LuCI Lua1 API 文档](https://github.com/openwrt/luci/wiki)
- [OpenWrt UCI 文档](https://openwrt.org/docs/guide-user/base-system/uci)
- [ES5 语法参考](https://www.ecma-international.org/ecma-262/5.1/)
+396
View File
@@ -0,0 +1,396 @@
![luci-app-easytier](https://socialify.git.ci/EasyTier/luci-app-easytier/image?custom_description=EasyTier+Installation+Packages+%28IPK+and+APK%29+for+OpenWrt&description=1&font=JetBrains+Mono&forks=1&issues=1&logo=https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F169161851%3Fs%3D200%26v%3D4&name=1&pulls=1&stargazers=1&theme=Auto)
# Development Guide
English | [简体中文](DEVELOPMENT.md)
This document details the development standards and requirements for luci-app-easytier.
## Core Principles
**Must be compatible with OpenWrt 18.06 ~ 26.x, using LuCI Lua1 architecture**
## Technology Stack Restrictions
### Backend (Lua)
#### ✅ Allowed
- Lua 5.1 standard syntax
- LuCI Lua1 API:
- `luci.controller`
- `luci.model.uci`
- `luci.http`
- `luci.sys`
- `nixio.fs`
- Standard libraries: `io`, `os`, `string`, `table`, `math`
#### ❌ Forbidden
- Lua2-specific syntax
- New LuCI JS API
- Any libraries requiring additional dependencies
### Frontend (JavaScript)
#### ✅ Allowed
- ES5 syntax
- `var` declarations
- `function` keyword
- `XMLHttpRequest`
- Native DOM manipulation
- Native event handling
#### ❌ Forbidden
```javascript
// Forbidden ES6+ syntax
let x = 1; // ❌
const y = 2; // ❌
var z = () => {}; // ❌ Arrow functions
fetch('/api'); // ❌
async/await // ❌
import/export // ❌
Template literals `${x}` // ❌
Destructuring // ❌
class syntax // ❌
// Forbidden frameworks
Vue/React/Angular // ❌
jQuery // ❌
Any build tools // ❌
```
#### ✅ Correct Example
```javascript
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/status', true);
xhr.onload = function() {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
document.getElementById('status').textContent = data.status;
}
};
xhr.send();
```
## Architecture Design
### Page Rendering Flow
```
User Request → Lua Controller → Render HTM Template → Return HTML
↓
Inject initial data into page
↓
JavaScript enhances interaction
↓
AJAX calls Lua API
↓
Update partial DOM
```
### File Organization
```
controller/easytier.lua
├── index() # Route registration
├── act_status() # Status API
├── upload_binary() # Upload API
└── ...
view/easytier/status.htm
├── Lua template code (<%...%>)
├── HTML structure
├── CSS styles (<style>)
└── JavaScript (<script>)
```
## Internationalization (i18n)
### Usage in Lua
```lua
local i18n = require "luci.i18n"
local translate = i18n.translate
-- Translate text
local text = translate("Hello World")
```
### Usage in HTM Templates
```html
<!-- Short syntax -->
<h1><%:Hello World%></h1>
<!-- Full syntax -->
<p><%=translate("Welcome to EasyTier")%></p>
```
### Usage in JavaScript
```javascript
// Method 1: Lua injects translation
var msg = '<%=translate("Upload successful")%>';
alert(msg);
// Method 2: API returns translation
xhr.onload = function() {
var response = JSON.parse(xhr.responseText);
alert(response.message); // Already translated by backend
};
```
### Translation Files
```po
# po/zh_Hans/easytier.po
msgid "Hello World"
msgstr "你好世界"
msgid "Upload successful"
msgstr "上传成功"
```
## API Design
### Route Registration
```lua
-- controller/easytier.lua
function index()
entry({"admin", "vpn", "easytier"}, firstchild(), _("EasyTier"), 46).dependent = true
entry({"admin", "vpn", "easytier", "status"}, cbi("easytier_status"), _("Status"), 1).leaf = true
entry({"admin", "vpn", "easytier", "api_status"}, call("act_status")).leaf = true
end
```
### API Implementation
```lua
function act_status()
local uci = require "luci.model.uci".cursor()
luci.http.prepare_content("application/json")
luci.http.write_json({
success = true,
running = luci.sys.call("pgrep easytier-core >/dev/null") == 0,
version = uci:get_first("easytier", "easytier", "version")
})
end
```
### Frontend Invocation
```javascript
function updateStatus() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/cgi-bin/luci/admin/vpn/easytier/api_status', true);
xhr.onload = function() {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
if (data.success) {
document.getElementById('status').textContent =
data.running ? 'Running' : 'Stopped';
}
}
};
xhr.send();
}
```
## UCI Configuration
### Reading Configuration
```lua
local uci = require "luci.model.uci".cursor()
-- Read single value
local enabled = uci:get("easytier", "@easytier[0]", "enabled")
-- Read first section
local network_name = uci:get_first("easytier", "easytier", "network_name")
-- Read list
local peers = uci:get("easytier", "@easytier[0]", "peers") or {}
```
### Saving Configuration
```lua
-- Set value
uci:set("easytier", "@easytier[0]", "enabled", "1")
uci:set("easytier", "@easytier[0]", "network_name", "mynet")
-- Set list
uci:delete("easytier", "@easytier[0]", "peers")
for _, peer in ipairs(peers) do
uci:set_list("easytier", "@easytier[0]", "peers", peer)
end
-- Commit changes
uci:commit("easytier")
```
## Log Handling
### Backend Implementation
```lua
function get_log()
local log_file = "/tmp/easytier.log"
local log = ""
if luci.sys.call("[ -f '" .. log_file .. "' ]") == 0 then
-- Remove ANSI color codes
log = luci.sys.exec("sed 's/\\x1b\\[[0-9;]*m//g' " .. log_file)
end
luci.http.write(log)
end
```
### Frontend Rendering
```javascript
function loadLog() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/cgi-bin/luci/admin/vpn/easytier/get_log', true);
xhr.onload = function() {
if (xhr.status === 200) {
var lines = xhr.responseText.split('\n');
var html = '';
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
var level = 'info';
if (line.indexOf('ERROR') !== -1) level = 'error';
else if (line.indexOf('WARN') !== -1) level = 'warn';
else if (line.indexOf('DEBUG') !== -1) level = 'debug';
html += '<div class="log-line log-' + level + '">' +
escapeHtml(line) + '</div>';
}
document.getElementById('log_content').innerHTML = html;
}
};
xhr.send();
}
function escapeHtml(text) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(text));
return div.innerHTML;
}
```
## UI Design Standards
### Responsive Layout
```css
/* Desktop */
.container {
max-width: 1200px;
margin: 0 auto;
}
/* Mobile */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.grid {
grid-template-columns: 1fr;
}
}
```
### Theme Adaptation
```css
:root {
--bg-color: #ffffff;
--text-color: #333333;
--border-color: #e0e0e0;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-color: #1e1e1e;
--text-color: #e0e0e0;
--border-color: #3e3e3e;
}
}
.card {
background: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
```
## Testing Requirements
### Compatibility Testing
Must be tested on the following environments:
- ✅ OpenWrt 18.06 (Lua1)
- ✅ OpenWrt 22.03 (Lua1/Lua2)
- ✅ OpenWrt SNAPSHOT (Lua2)
### Browser Testing
- ✅ Chrome/Edge (modern browsers)
- ✅ Firefox
- ✅ Safari (iOS)
- ✅ Mobile browsers
### Functional Testing
- [ ] Configuration save and load
- [ ] Service start and stop
- [ ] File upload
- [ ] Log display
- [ ] Status updates
- [ ] Internationalization switching
## FAQ
### Q: Why can't I use let/const?
A: The browser environment in OpenWrt 18.06 doesn't support ES6; ES5 syntax must be used.
### Q: Why not use Lua2 and JS frameworks?
A: Cannot be compatible with older versions of OpenWrt, and JS frameworks are not suitable for low-spec routers.
### Q: Why can't I use Vue/React?
A: These frameworks require build tools and have large file sizes, unsuitable for embedded environments.
### Q: How to implement dynamic updates?
A: Use XMLHttpRequest + setInterval polling, or WebSocket (requires additional support).
### Q: How to debug?
A: Use browser developer tools, check Console and Network tabs.
## Code Review Checklist
Before submitting, please confirm:
- [ ] No ES6+ syntax used
- [ ] No frontend frameworks used
- [ ] All text is internationalized
- [ ] APIs return JSON format
- [ ] Error handling is complete
- [ ] Code has appropriate comments
- [ ] Tested on OpenWrt 18.06
- [ ] Mobile display is normal
- [ ] Dark theme display is normal
## Reference Resources
- [LuCI Lua1 API Documentation](https://github.com/openwrt/luci/wiki)
- [OpenWrt UCI Documentation](https://openwrt.org/docs/guide-user/base-system/uci)
- [ES5 Syntax Reference](https://www.ecma-international.org/ecma-262/5.1/)
+190
View File
@@ -0,0 +1,190 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2024-2026 luci-app-easytier Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+268
View File
@@ -0,0 +1,268 @@
![luci-app-easytier](https://socialify.git.ci/EasyTier/luci-app-easytier/image?description=1&font=JetBrains+Mono&forks=1&issues=1&logo=https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F169161851%3Fs%3D200%26v%3D4&name=1&pulls=1&stargazers=1&theme=Auto)
[English](README_EN.md) | 简体中文
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![OpenWrt](https://img.shields.io/badge/OpenWrt-18.06--26.x-orange.svg)](https://openwrt.org)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/EasyTier/luci-app-easytier)
OpenWrt LuCI界面,用于管理 [EasyTier](https://github.com/EasyTier/EasyTier) - 一个简单、安全、去中心化的内网穿透VPN组网方案。
## UI 预览
<img width="1584" height="2523" alt="image" src="https://github.com/user-attachments/assets/afa1d31b-cb97-4502-a5ac-2ada8c09f78d" />
<img width="1386" height="1758" alt="image" src="https://github.com/user-attachments/assets/c65bf3fe-5c8b-42c3-8093-96c076350846" />
## ✨ 特性
- 🎨 现代化UI设计,支持亮色/暗色主题自动切换
- 📱 完美适配移动端和桌面端
- 🌍 完整的中文/英文国际化支持
- 📊 实时流量监控和网卡状态显示
- 🔄 实时显示性能占用和版本信息
- 📦 支持压缩包和二进制文件上传
- 🔧 灵活的配置管理和备份恢复
## 📋 兼容性
### OpenWrt版本
- ✅ OpenWrt 18.06 / 18.08
- ✅ OpenWrt 19.x ~ 26.x
- ✅ 支持 IPK (22.03.x) 和 APK (SNAPSHOT) 包格式
### 架构支持
- ARM: aarch64, armv7, arm
- MIPS: mips, mipsel
- x86: x86_64
## 🚀 快速开始
### 方式一:使用预编译包
1. 从 [Releases](https://github.com/EasyTier/luci-app-easytier/releases) 下载对应架构的安装包
2. 上传到OpenWrt的 `/tmp` 目录
3. 安装:
```bash
# IPK包 (OpenWrt 22.03.x)
opkg install /tmp/luci-app-easytier_*.ipk
# APK包 (OpenWrt SNAPSHOT)
apk add --allow-untrusted /tmp/luci-app-easytier_*.apk
```
4. 刷新浏览器或重新登录LuCI界面
5. 在 **VPN → EasyTier** 中上传EasyTier二进制程序
### 方式二:GitHub Actions自动编译
1. Fork本项目
2. 修改 `.github/workflows/build.yml` 中的架构列表(可选)
3. 进入 Actions 页面,手动触发 `Build-OpenWrt-EasyTier` 工作流
4. 填写版本号(如 `v1.0.0`)和发布说明
5. 等待编译完成,在 Releases 中下载
<img width="2727" height="866" alt="image" src="https://github.com/user-attachments/assets/24a55d1c-7937-4cef-87f8-cd8778b5f009" />
### 方式三:本地编译
```bash
# 1. 下载OpenWrt SDK
wget https://downloads.openwrt.org/releases/22.03.7/targets/rockchip/armv8/openwrt-sdk-22.03.7-rockchip-armv8_gcc-11.2.0_musl.Linux-x86_64.tar.xz
tar -xJf openwrt-sdk-*.tar.xz
cd openwrt-sdk-*/
# 2. 克隆项目
git clone https://github.com/EasyTier/luci-app-easytier.git package/luci-app-easytier
# 3. 更新feeds并配置
./scripts/feeds update -a
./scripts/feeds install -a
make defconfig
# 4. 编译
make package/luci-app-easytier/compile V=s
# 5. 查找生成的ipk
find bin/ -name "luci-app-easytier*.ipk"
```
## 📦 依赖
- `kmod-tun` - TUN/TAP内核模块(必需)
- `luci-compat` - LuCI兼容层
安装前请确保已安装 `kmod-tun`:
```bash
opkg update
opkg install kmod-tun
```
## 🔧 使用说明
### 首次配置
1. 安装插件后,进入 **VPN → EasyTier**
2. 在 **上传程序** 页面上传EasyTier二进制文件或直接安装包含核心的ipk/apk包(easytier.ipk easytier.apk)
- 支持单个文件:`easytier-core`, `easytier-cli`, `easytier-web-embed`
- 支持压缩包:`.zip`, `.tar.gz`, `.tar`
3. 在 **EasyTier Core** 页面配置网络参数
4. 启用并保存配置
### 配置方式
支持两种配置方式:
1. **命令行参数**:通过LuCI界面配置各项参数
2. **配置文件**:使用 `/etc/easytier/config.toml` 配置文件
### 功能页面
- **状态** - 查看运行状态、版本信息、连接信息和实时流量
- **EasyTier Core** - 配置核心参数(网络名称、密钥、节点等)
- **自建控制台** - 配置easytier-web控制台
- **日志** - 查看运行日志,支持级别过滤
- **上传程序** - 上传和管理EasyTier二进制文件
## 🛠️ 开发指南
### 技术栈
- **后端**: Lua 5.1 (LuCI旧架构)
- **前端**: HTML + CSS + ES5 JavaScript
- **渲染**: 服务器端渲染 (SSR)
- **国际化**: LuCI原生i18n (.po文件)
### 开发要求
#### 兼容性原则
- ✅ 必须兼容 OpenWrt 18.06 ~ 26.x
- ✅ 使用 LuCI Lua1 架构(同时兼容Lua2)
- ❌ 禁止使用 LuCI JS框架 (view.js/form.js/rpc.js)
- ❌ 禁止使用 ES6+ 语法 (let/const/箭头函数/fetch)
#### 前端规范
```javascript
// ✅ 允许
var xhr = new XMLHttpRequest();
function handleClick() { }
document.getElementById('id');
// ❌ 禁止
const data = await fetch('/api');
let result = () => {};
import module from 'module';
```
#### 后端规范
```lua
-- ✅ 使用 Lua1 API
local http = require "luci.http"
local uci = require "luci.model.uci".cursor()
http.write_json({success = true})
-- ❌ 禁止 Lua2 特有语法
-- 不使用新版 LuCI JS API
```
#### 国际化
```lua
-- Lua中
local translate = i18n.translate
translate("Text to translate")
-- HTM模板中
<%:Text to translate%>
-- JavaScript中(由Lua注入)
var msg = '<%=translate("Text")%>';
```
### 目录结构
```
luci-app-easytier/
├── luasrc/
│ ├── controller/
│ │ └── easytier.lua # 路由和API控制器
│ ├── model/cbi/
│ │ ├── easytier.lua # 配置页面
│ │ └── easytier_status.lua # 状态页面
│ └── view/easytier/
│ ├── easytier_status.htm # 状态页面模板
│ ├── easytier_log.htm # 日志页面
│ ├── easytier_upload.htm # 上传页面
│ └── ...
├── root/
│ ├── etc/
│ │ ├── config/easytier # UCI配置文件
│ │ ├── init.d/easytier # 启动脚本
│ │ └── easytier/ # 配置目录
│ └── usr/share/easytier/
│ └── download.sh # 下载脚本
├── po/
│ ├── zh_Hans/easytier.po # 简体中文翻译
│ └── templates/easytier.pot # 翻译模板
└── Makefile # OpenWrt包定义
```
### API设计规范
```lua
-- API路径格式
entry({"admin", "vpn", "easytier", "api_name"}, call("function_name")).leaf = true
-- 返回JSON格式
function api_name()
luci.http.prepare_content("application/json")
luci.http.write_json({
success = true,
data = {},
message = "Success"
})
end
```
### 提交代码
1. Fork项目并创建分支
2. 确保代码符合开发规范
3. 测试在 OpenWrt 18.06 和最新版本上的兼容性
4. 提交Pull Request
详细开发规范请参考 [开发指南](DEVELOPMENT.md)
## 🐛 故障排除
### 安装后界面不显示
```bash
# 清除LuCI缓存
rm -rf /tmp/luci-*
# 重新登录LuCI界面
```
### util.pcdata 警告
```bash
sed -i 's/util.pcdata/xml.pcdata/g' /usr/lib/lua/luci/model/cbi/easytier.lua
```
### 配置文件冲突
升级时如果提示配置文件冲突,旧配置会保留,新配置保存为 `/etc/config/easytier-opkg`,可以忽略此报错。
## 📄 许可证
本项目采用 [Apache License 2.0](LICENSE) 许可证。
## 🔗 相关链接
- [EasyTier 官方仓库](https://github.com/EasyTier/EasyTier)
- [EasyTier 官方文档](https://easytier.cn)
- [OpenWrt 官网](https://openwrt.org)
## 🤝 贡献
欢迎提交 Issue 和 Pull Request!
---
+259
View File
@@ -0,0 +1,259 @@
![luci-app-easytier](https://socialify.git.ci/EasyTier/luci-app-easytier/image?custom_description=EasyTier+Installation+Packages+%28IPK+and+APK%29+for+OpenWrt&description=1&font=JetBrains+Mono&forks=1&issues=1&logo=https%3A%2F%2Favatars.githubusercontent.com%2Fu%2F169161851%3Fs%3D200%26v%3D4&name=1&pulls=1&stargazers=1&theme=Auto)
English | [简体中文](README.md)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![OpenWrt](https://img.shields.io/badge/OpenWrt-18.06--26.x-orange.svg)](https://openwrt.org)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/EasyTier/luci-app-easytier)
OpenWrt LuCI web interface for managing [EasyTier](https://github.com/EasyTier/EasyTier) - a simple, secure, and decentralized VPN networking solution.
## ✨ Features
- 🎨 Modern UI design with automatic light/dark theme switching
- 📱 Perfect adaptation for mobile and desktop devices
- 🌍 Full Chinese/English internationalization support
- 📊 Real-time traffic monitoring and network interface status display
- 🔄 Real-time performance metrics and version information
- 📦 Support for compressed packages and binary file uploads
- 🔧 Flexible configuration management with backup and restore
## 📋 Compatibility
### OpenWrt Versions
- ✅ OpenWrt 18.06 / 18.08
- ✅ OpenWrt 19.x ~ 26.x
- ✅ Support for IPK (22.03.x) and APK (SNAPSHOT) package formats
### Architecture Support
- ARM: aarch64, armv7, arm
- MIPS: mips, mipsel
- x86: x86_64
## 🚀 Quick Start
### Method 1: Using Pre-compiled Packages
1. Download the package for your architecture from [Releases](https://github.com/EasyTier/luci-app-easytier/releases)
2. Upload to OpenWrt's `/tmp` directory
3. Install:
```bash
# IPK package (OpenWrt 22.03.x)
opkg install /tmp/luci-app-easytier_*.ipk
# APK package (OpenWrt SNAPSHOT)
apk add --allow-untrusted /tmp/luci-app-easytier_*.apk
```
4. Refresh your browser or re-login to LuCI interface
5. Upload EasyTier binary in **VPN → EasyTier**
### Method 2: GitHub Actions Auto-build
1. Fork this repository
2. Modify the architecture list in `.github/workflows/build.yml` (optional)
3. Go to Actions page and manually trigger the `Build-OpenWrt-EasyTier` workflow
4. Enter version number (e.g., `v1.0.0`) and release notes
5. Wait for compilation to complete and download from Releases
<img width="2727" height="866" alt="image" src="https://github.com/user-attachments/assets/24a55d1c-7937-4cef-87f8-cd8778b5f009" />
### Method 3: Local Compilation
```bash
# 1. Download OpenWrt SDK
wget https://downloads.openwrt.org/releases/22.03.7/targets/rockchip/armv8/openwrt-sdk-22.03.7-rockchip-armv8_gcc-11.2.0_musl.Linux-x86_64.tar.xz
tar -xJf openwrt-sdk-*.tar.xz
cd openwrt-sdk-*/
# 2. Clone the repository
git clone https://github.com/EasyTier/luci-app-easytier.git package/luci-app-easytier
# 3. Update feeds and configure
./scripts/feeds update -a
./scripts/feeds install -a
make defconfig
# 4. Compile
make package/luci-app-easytier/compile V=s
# 5. Find the generated ipk
find bin/ -name "luci-app-easytier*.ipk"
```
## 📦 Dependencies
- `kmod-tun` - TUN/TAP kernel module (required)
- `luci-compat` - LuCI compatibility layer
Ensure `kmod-tun` is installed before installation:
```bash
opkg update
opkg install kmod-tun
```
## 🔧 Usage
### Initial Configuration
1. After installing the plugin, navigate to **VPN → EasyTier**
2. Upload EasyTier binary files on the **Upload Program** page, or directly install the ipk/apk package containing the core (easytier.ipk / easytier.apk)
- Supports single files: `easytier-core`, `easytier-cli`, `easytier-web-embed`
- Supports compressed packages: `.zip`, `.tar.gz`, `.tar`
3. Configure network parameters on the **EasyTier Core** page
4. Enable and save the configuration
### Configuration Methods
Two configuration methods are supported:
1. **Command-line parameters**: Configure parameters through the LuCI interface
2. **Configuration file**: Use the `/etc/easytier/config.toml` configuration file
### Function Pages
- **Status** - View running status, version info, connection info, and real-time traffic
- **EasyTier Core** - Configure core parameters (network name, secret key, nodes, etc.)
- **Self-hosted Console** - Configure easytier-web console
- **Logs** - View running logs with level filtering support
- **Upload Program** - Upload and manage EasyTier binary files
## 🛠️ Development Guide
### Tech Stack
- **Backend**: Lua 5.1 (LuCI legacy architecture)
- **Frontend**: HTML + CSS + ES5 JavaScript
- **Rendering**: Server-side rendering (SSR)
- **Internationalization**: LuCI native i18n (.po files)
### Development Requirements
#### Compatibility Principles
- ✅ Must be compatible with OpenWrt 18.06 ~ 26.x
- ✅ Use LuCI Lua1 architecture (also compatible with Lua2)
- ❌ Do not use LuCI JS framework (view.js/form.js/rpc.js)
- ❌ Do not use ES6+ syntax (let/const/arrow functions/fetch)
#### Frontend Standards
```javascript
// ✅ Allowed
var xhr = new XMLHttpRequest();
function handleClick() { }
document.getElementById('id');
// ❌ Forbidden
const data = await fetch('/api');
let result = () => {};
import module from 'module';
```
#### Backend Standards
```lua
-- ✅ Use Lua1 API
local http = require "luci.http"
local uci = require "luci.model.uci".cursor()
http.write_json({success = true})
-- ❌ Do not use Lua2-specific syntax
-- Do not use new LuCI JS API
```
#### Internationalization
```lua
-- In Lua
local translate = i18n.translate
translate("Text to translate")
-- In HTM templates
<%:Text to translate%>
-- In JavaScript (injected by Lua)
var msg = '<%=translate("Text")%>';
```
### Directory Structure
```
luci-app-easytier/
├── luasrc/
│ ├── controller/
│ │ └── easytier.lua # Routes and API controller
│ ├── model/cbi/
│ │ ├── easytier.lua # Configuration page
│ │ └── easytier_status.lua # Status page
│ └── view/easytier/
│ ├── easytier_status.htm # Status page template
│ ├── easytier_log.htm # Log page
│ ├── easytier_upload.htm # Upload page
│ └── ...
├── root/
│ ├── etc/
│ │ ├── config/easytier # UCI config file
│ │ ├── init.d/easytier # Init script
│ │ └── easytier/ # Config directory
│ └── usr/share/easytier/
│ └── download.sh # Download script
├── po/
│ ├── zh_Hans/easytier.po # Simplified Chinese translation
│ └── templates/easytier.pot # Translation template
└── Makefile # OpenWrt package definition
```
### API Design Standards
```lua
-- API path format
entry({"admin", "vpn", "easytier", "api_name"}, call("function_name")).leaf = true
-- Return JSON format
function api_name()
luci.http.prepare_content("application/json")
luci.http.write_json({
success = true,
data = {},
message = "Success"
})
end
```
### Contributing Code
1. Fork the project and create a branch
2. Ensure code complies with development standards
3. Test compatibility on OpenWrt 18.06 and the latest version
4. Submit a Pull Request
## 🐛 Troubleshooting
### Interface Not Showing After Installation
```bash
# Clear LuCI cache
rm -rf /tmp/luci-*
# Re-login to LuCI interface
```
### util.pcdata Warning
```bash
sed -i 's/util.pcdata/xml.pcdata/g' /usr/lib/lua/luci/model/cbi/easytier.lua
```
### Configuration File Conflict
When upgrading, if a configuration file conflict is prompted, the old configuration will be retained and the new configuration saved as `/etc/config/easytier-opkg`. You can ignore this error.
## 📄 License
This project is licensed under the [Apache License 2.0](LICENSE).
## 🔗 Related Links
- [EasyTier Official Repository](https://github.com/EasyTier/EasyTier)
- [EasyTier Official Documentation](https://easytier.cn)
- [OpenWrt Official Website](https://openwrt.org)
## 🤝 Contributing
Issues and Pull Requests are welcome!
---
+70
View File
@@ -0,0 +1,70 @@
include $(TOPDIR)/rules.mk
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
PKG_NAME:=easytier-noweb
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.2)
ifeq ($(ARCH),mipsel)
APP_ARCH:=mipsel
endif
ifeq ($(ARCH),mips)
APP_ARCH:=mips
endif
ifeq ($(ARCH),arm)
APP_ARCH:=arm
endif
ifeq ($(BOARD),kirkwood)
APP_ARCH:=arm
endif
ifeq ($(ARCH),armv7)
APP_ARCH:=armv7
endif
ifeq ($(ARCH),aarch64)
APP_ARCH:=aarch64
endif
ifeq ($(ARCH),arm64)
APP_ARCH:=aarch64
endif
ifeq ($(ARCH),armv8)
APP_ARCH:=aarch64
endif
ifeq ($(ARCH),x86_64)
APP_ARCH:=x86_64
endif
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
TITLE:=A simple, decentralized mesh VPN with WireGuard support.
DEPENDS:=@(x86_64||arm||aarch64||mipsel||mips) +kmod-tun
URL:=https://github.com/EasyTier/EasyTier
MENU:=1
endef
define Package/$(PKG_NAME)/description
A simple, decentralized mesh VPN with WireGuard support.
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
if [ ! -f $(PKG_BUILD_DIR)/easytier-core ]; then \
wget https://github.com/EasyTier/EasyTier/releases/download/v$(PKG_VERSION)/easytier-linux-$(APP_ARCH)-v$(PKG_VERSION).zip -O $(PKG_BUILD_DIR)/easytier-$(PKG_VERSION).zip; \
unzip -o -j $(PKG_BUILD_DIR)/easytier-$(PKG_VERSION).zip -d $(PKG_BUILD_DIR); \
rm -f $(PKG_BUILD_DIR)/easytier-$(PKG_VERSION).zip; \
fi
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/easytier-core $(1)/usr/bin/easytier-core
$(INSTALL_BIN) $(PKG_BUILD_DIR)/easytier-cli $(1)/usr/bin/easytier-cli
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
+88
View File
@@ -0,0 +1,88 @@
include $(TOPDIR)/rules.mk
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
PKG_NAME:=easytier
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.2)
ifeq ($(ARCH),mipsel)
APP_ARCH:=mipsel
endif
ifeq ($(ARCH),mips)
APP_ARCH:=mips
endif
ifeq ($(ARCH),arm)
APP_ARCH:=arm
endif
ifeq ($(BOARD),kirkwood)
APP_ARCH:=arm
endif
ifeq ($(ARCH),armv7)
APP_ARCH:=armv7
endif
ifeq ($(ARCH),aarch64)
APP_ARCH:=aarch64
endif
ifeq ($(ARCH),arm64)
APP_ARCH:=aarch64
endif
ifeq ($(ARCH),armv8)
APP_ARCH:=aarch64
endif
ifeq ($(ARCH),x86_64)
APP_ARCH:=x86_64
endif
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
RSTRIP:=:
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
TITLE:=A simple, decentralized mesh VPN with WireGuard support.
DEPENDS:=@(x86_64||arm||aarch64||mipsel||mips) +kmod-tun
URL:=https://github.com/EasyTier/EasyTier
MENU:=1
endef
define Package/$(PKG_NAME)/description
A simple, decentralized mesh VPN with WireGuard support.
endef
define Package/$(PKG_NAME)/config
config EASYTIER_INCLUDE_WEBCONSOLE
bool "Include Web Console (easytier-web)"
depends on PACKAGE_$(PKG_NAME)
depends on !(mips || mipsel)
default y
help
Install the easytier-web web console.
endef
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
if [ ! -f $(PKG_BUILD_DIR)/easytier-core ]; then \
wget https://github.com/EasyTier/EasyTier/releases/download/v$(PKG_VERSION)/$(PKG_NAME)-linux-$(APP_ARCH)-v$(PKG_VERSION).zip -O $(PKG_BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION).zip; \
unzip -o -j $(PKG_BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION).zip -d $(PKG_BUILD_DIR); \
rm -f $(PKG_BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION).zip; \
fi
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/bin
$(CP) $(PKG_BUILD_DIR)/easytier-core $(1)/usr/bin/
chmod 0755 $(1)/usr/bin/easytier-core
$(CP) $(PKG_BUILD_DIR)/easytier-cli $(1)/usr/bin/
chmod 0755 $(1)/usr/bin/easytier-cli
ifdef CONFIG_EASYTIER_INCLUDE_WEBCONSOLE
$(CP) $(PKG_BUILD_DIR)/easytier-web-embed $(1)/usr/bin/easytier-web
chmod 0755 $(1)/usr/bin/easytier-web
endif
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
@@ -0,0 +1,59 @@
#
# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
#
# This is free software, licensed under the Apache License, Version 2.0 .
#
include $(TOPDIR)/rules.mk
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.6.2)
PKG_RELEASE:=1
LUCI_TITLE:=LuCI support for EasyTier
LUCI_DEPENDS:=+kmod-tun +luci-compat
LUCI_PKGARCH:=all
PKG_NAME:=luci-app-easytier
define Package/$(PKG_NAME)/conffiles
/etc/config/easytier
/etc/easytier/
endef
define Package/$(PKG_NAME)/prerm
#!/bin/sh
if [ -f /etc/config/easytier ] ; then
echo "备份luci配置文件/etc/config/easytier到/tmp/easytier_backup"
echo "不重启设备之前再次安装luci-app-easytier 配置不丢失,无需重新配置"
cp -rf /etc/config/easytier /tmp/easytier_backup
fi
if ls /etc/easytier/* >/dev/null 2>&1; then
echo "备份easytier配置目录/etc/easytier到/tmp/et_config_backup"
echo "不重启设备之前再次安装luci-app-easytier 配置不丢失,无需重新配置"
cp -rf /etc/easytier /tmp/et_config_backup
fi
endef
define Package/$(PKG_NAME)/postinst
#!/bin/sh
chmod +x /etc/init.d/easytier
if [ -f /tmp/easytier_backup ] ; then
echo "发现luci备份配置文件/tmp/easytier_backup,开始恢复到/etc/config/easytier"
rm -rf /etc/config/easytier
mv -f /tmp/easytier_backup /etc/config/easytier
echo "请前往 VPN - EasyTier 界面进行重启插件"
fi
if [ -d /tmp/et_config_backup ] ; then
echo "发现easytier备份配置目录/tmp/et_config_backup,开始恢复到/etc/easytier"
rm -rf /etc/easytier
mv -f /tmp/et_config_backup /etc/easytier
echo "请前往 VPN - EasyTier 界面进行重启插件"
fi
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,475 @@
local http = luci.http
local nixio = require "nixio"
m = Map("easytier")
-- easytier-core
s=m:section(TypedSection, "easytier")
s.addremove=false
s.anonymous=true
s:tab("general", translate("General Settings"))
s:tab("privacy", translate("Advanced Settings"))
switch = s:taboption("general",Flag, "enabled", translate("Enable"))
switch.rmempty = false
etcmd = s:taboption("general", ListValue, "etcmd", translate("Startup Method"),
translate("Official Web Console: <a href=\"https://easytier.cn/web\" target=\"_blank\">https://easytier.cn/web</a><br>"
.. "Official Configuration File Generator: <a href=\"https://easytier.cn/web/index.html#/config_generator\" target=\"_blank\">"
.. "https://easytier.cn/web/index.html#/config_generator</a><br>Please note to set the RPC port to 15888"))
etcmd.default = "etcmd"
etcmd:value("etcmd", translate("Default"))
etcmd:value("config", translate("Configuration File"))
etcmd:value("web", translate("Web Configuration"))
et_config = s:taboption("general", TextValue, "et_config", translate("Configuration File"),
translate("The configuration file is located at /etc/easytier/config.toml<br>"
.. "The command-line startup parameters and the parameters in this configuration file are not synchronized<br>"
.. "Make sure to specify the TUN interface name and port to enable automatic firewall allowance"))
et_config.rows = 18
et_config.wrap = "off"
et_config:depends("etcmd", "config")
et_config.cfgvalue = function(self, section)
return nixio.fs.readfile("/etc/easytier/config.toml") or ""
end
et_config.write = function(self, section, value)
local dir = "/etc/easytier/"
local file = dir .. "config.toml"
-- 检查目录是否存在,如果不存在则创建
if not nixio.fs.access(dir) then
nixio.fs.mkdir(dir)
end
nixio.fs.writefile(file, value:gsub("\r\n", "\n"))
end
web_config = s:taboption("general", Value, "web_config", translate("Web Server Address"),
translate("Web configuration server address. (-w parameter)<br>"
.. "For a self-hosted Web server, use the format: udp://server_address:22020/username<br>"
.. "For the official Web server, use the format: username<br>"
.. "Official Web Console: <a href='https://easytier.cn/web'>easytier.cn/web</a>"))
web_config.placeholder = "admin"
web_config:depends("etcmd", "web")
network_name = s:taboption("general", Value, "network_name", translate("Network Name"),
translate("The network name used to identify this VPN network (--network-name parameter)"))
network_name.password = true
network_name.placeholder = "easytier-name"
network_name.maxlength = 64
network_name.validate = function(self, value)
if value and value ~= "" and value:match("[^%w%-_]") then
return nil, translate("Only alphanumeric characters, hyphens and underscores allowed")
end
return value
end
network_name:depends("etcmd", "etcmd")
network_secret = s:taboption("general", Value, "network_secret", translate("Network Secret"),
translate("Network secret used to verify whether this node belongs to the VPN network (--network-secret parameter)"))
network_secret.password = true
network_secret.placeholder = "easytier-password"
network_secret.maxlength = 128
network_secret:depends("etcmd", "etcmd")
ip_dhcp = s:taboption("general", Flag, "ip_dhcp", translate("Enable DHCP"),
translate("IP address will be automatically determined and assigned by EasyTier, starting from 10.0.0.1 by default. "
.. "Warning: When using DHCP, if an IP conflict occurs in the network, the IP will be automatically changed. (-d parameter)"))
ip_dhcp:depends("etcmd", "etcmd")
ipaddr = s:taboption("general", Value, "ipaddr", translate("Interface IP Address"),
translate("The IPv4 address of this VPN node. If left empty, this node will only forward packets and will not "
.. "create a TUN device. (-i parameter)"))
ipaddr.datatype = "ip4addr"
ipaddr.placeholder = "10.0.0.1"
ipaddr:depends("etcmd", "etcmd")
ip6addr = s:taboption("general", Value, "ip6addr", translate("Interface IPV6 Address"),
translate("ipv6 address of this vpn node, can be used together with ipv4 for dual-stack operation"
.. "(--ipv6 parameter)"))
ip6addr.datatype = "ip6addr"
ip6addr.placeholder = "2001:db8::1"
ip6addr:depends("etcmd", "etcmd")
peeradd = s:taboption("general", DynamicList, "peeradd", translate("Peer Nodes"),
translate("Initial connected peer nodes (-p parameter)<br>"
.. "Public server status check: <a href='https://uptime.easytier.cn' target='_blank'>"
.. "Click here to check</a>"))
peeradd:depends("etcmd", "etcmd")
--[=[
external_node = s:taboption("general", Value, "external_node", translate("Shared Node Address"),
translate("Use a public shared node to discover peer nodes, same function as the parameter above (-e parameter)"))
external_node.default = ""
external_node.placeholder = "tcp://public.easytier.top:11010"
external_node:value("tcp://public.easytier.top:11010", translate("Official Server - tcp://public.easytier.top:11010"))
external_node:depends("etcmd", "etcmd")
]=]
proxy_network = s:taboption("general", DynamicList, "proxy_network", translate("Subnet Proxy"),
translate("Export the local network to other peers in the VPN, allowing access to other devices in the current LAN (-n parameter)"))
proxy_network:depends("etcmd", "etcmd")
mapped_listeners = s:taboption("privacy", DynamicList, "mapped_listeners", translate("Public Addresses of Specified Listeners"),
translate("Manually specify the public IP address of this machine, so other nodes can connect to this node using "
.. "that address (domain names not supported).<br>For example: tcp://123.123.123.123:11223, multiple entries "
.. "can be specified. (--mapped-listeners parameter)"))
mapped_listeners:depends("listenermode", "ON")
rpc_portal = s:taboption("privacy", Value, "rpc_portal", translate("Portal Address Port"),
translate("RPC portal address used for management. 0 means a random port, 12345 means listening on port 12345 on localhost, "
.. "0.0.0.0:12345 means listening on port 12345 on all interfaces.<br>The default is 0; it is recommended to "
.. "use 15888 to avoid failure in obtaining status information (-r parameter)"))
rpc_portal.placeholder = "15888"
rpc_portal.default = "15888"
rpc_portal.datatype = "range(1,65535)"
rpc_portal:depends("etcmd", "etcmd")
rpc_portal_whitelist = s:taboption("privacy", Value, "rpc_portal_whitelist", translate("RPC Access Whitelist"),
translate("rpc portal whitelist, only allow these addresses to access rpc portal (--rpc-portal-whitelist parameter)"))
rpc_portal_whitelist.placeholder = "127.0.0.1/32,127.0.0.0/8,::1/128"
rpc_portal_whitelist:depends("etcmd", "etcmd")
listenermode = s:taboption("general", ListValue, "listenermode", translate("Listener Port"),
translate("OFF: Do not listen on any port, only connect to peer nodes (--no-listener parameter)<br>"
.. "If used purely as a client (not as a server), you can choose not to listen on a port"))
listenermode:value("ON", translate("Listen"))
listenermode:value("OFF", translate("Do Not Listen"))
listenermode.default = "ON"
listenermode:depends("etcmd", "etcmd")
tcp_port = s:taboption("general", Value, "tcp_port", translate("TCP/UDP Port"),
translate("TCP/UDP protocol port number: 11010 means TCP/UDP will listen on port 11010.<br>"
.. "If this is the Web configuration in the config file, please fill in the same listening port for firewall allowance."))
tcp_port.datatype = "range(1,65535)"
tcp_port.default = "11010"
tcp_port:depends("listenermode", "ON")
tcp_port:depends("etcmd", "web")
ws_port = s:taboption("general", Value, "ws_port", translate("WS Port"),
translate("WS protocol port number: 11011 means WS will listen on port 11011.<br>"
.. "If this is the Web configuration in the config file, please fill in the same listening port for firewall allowance."))
ws_port.datatype = "range(1,65535)"
ws_port.default = "11011"
ws_port:depends("listenermode", "ON")
ws_port:depends("etcmd", "web")
wss_port = s:taboption("general", Value, "wss_port", translate("WSS Port"),
translate("WSS protocol port number: 11012 means WSS will listen on port 11012.<br>"
.. "If this is the Web configuration in the config file, please fill in the same listening port for firewall allowance."))
wss_port.datatype = "range(1,65535)"
wss_port.default = "11012"
wss_port:depends("listenermode", "ON")
wss_port:depends("etcmd", "web")
wg_port = s:taboption("general", Value, "wg_port", translate("WG Port"),
translate("WireGuard protocol port number: 11011 means WG will listen on port 11011.<br>"
.. "If this is the Web configuration in the config file, please fill in the same listening port for firewall allowance."))
wg_port.datatype = "range(1,65535)"
wg_port.placeholder = "11011"
wg_port:depends("listenermode", "ON")
wg_port:depends("etcmd", "web")
quic_port = s:taboption("general", Value, "quic_port", translate("QUIC Port"),
translate("If this is the Web configuration in the config file, please fill in the same listening port for firewall allowance."))
quic_port.datatype = "range(1,65535)"
quic_port:depends("listenermode", "ON")
quic_port:depends("etcmd", "web")
local model = nixio.fs.readfile("/proc/device-tree/model") or ""
local hostname = nixio.fs.readfile("/proc/sys/kernel/hostname") or ""
model = model:gsub("\n", "")
hostname = hostname:gsub("\n", "")
local device_name_default = (model ~= "" and model) or (hostname ~= "" and hostname) or "OpenWrt"
device_name_default = device_name_default:gsub(" ", "_")
hostname_opt = s:taboption("general", Value, "desvice_name", translate("Hostname"),
translate("The hostname used to identify this device (--hostname parameter)"))
hostname_opt.placeholder = device_name_default
hostname_opt.default = device_name_default
hostname_opt:depends("etcmd", "etcmd")
hostname_opt:depends("etcmd", "web")
uuid = s:taboption("general", Value, "uuid", translate("UUID"),
translate("Unique identifier used to recognize this device when connecting to the web console, for issuing configuration files"))
uuid.rows = 1
uuid.wrap = "off"
uuid:depends("etcmd", "web")
uuid.cfgvalue = function(self, section)
return nixio.fs.readfile("/etc/easytier/et_machine_id") or ""
end
uuid.write = function(self, section, value)
nixio.fs.writefile("/etc/easytier/et_machine_id", value:gsub("\r\n", "\n"))
end
instance_name = s:taboption("privacy", Value, "instance_name", translate("Instance Name"),
translate("Used to identify the VPN node instance on the same machine. (-m parameter)"))
instance_name.placeholder = "default"
instance_name:depends("etcmd", "etcmd")
vpn_portal = s:taboption("privacy", Value, "vpn_portal", translate("VPN Portal URL"),
translate("Defines the URL of the VPN portal, allowing other VPN clients to connect.<br>"
.. "Example: wg://0.0.0.0:11011/10.14.14.0/24 means the VPN portal is a WireGuard server listening on vpn."
.. "example.com:11010, and the VPN clients are in the 10.14.14.0/24 network (--vpn-portal parameter)"))
vpn_portal.placeholder = "wg://0.0.0.0:11011/10.14.14.0/24"
vpn_portal:depends("etcmd", "etcmd")
mtu = s:taboption("privacy", Value, "mtu", translate("MTU"),
translate("MTU for the TUN device, default is 1380 when unencrypted, and 1360 when encrypted"))
mtu.datatype = "range(1,1500)"
mtu.placeholder = "1300"
mtu:depends("etcmd", "etcmd")
default_protocol = s:taboption("privacy", ListValue, "default_protocol", translate("Default Protocol"),
translate("The default protocol used when connecting to peer nodes (--default-protocol parameter)"))
default_protocol:value("-",translate("default"))
default_protocol:value("tcp")
default_protocol:value("udp")
default_protocol:value("ws")
default_protocol:value("wss")
default_protocol:depends("etcmd", "etcmd")
tunname = s:taboption("privacy", Value, "tunname", translate("Virtual Network Interface Name"),
translate("Custom name for the virtual TUN interface (--dev-name parameter)<br>"
.. "If using web configuration, please use the same virtual network interface name as in the web config for firewall allowance"))
tunname.placeholder = "tun0"
tunname:depends("etcmd", "etcmd")
tunname:depends("etcmd", "web")
disable_encryption = s:taboption("privacy", Flag, "disable_encryption", translate("Disable Encryption"),
translate("Disable encryption for communication with peer nodes. "
.. "If encryption is disabled, all other nodes must also have encryption disabled (-u parameter)"))
disable_encryption:depends("etcmd", "etcmd")
encryption_algorithm = s:taboption("privacy", ListValue, "encryption_algorithm", translate("Encryption Algorithm"),
translate("encryption algorithm to use, supported: xor, chacha20, aes-gcm, aes-256-gcm, openssl-aes-gcm, openssl-chacha20, openssl-aes-256-gcm. default (aes-gcm) (--encryption-algorithm parameter)"))
encryption_algorithm.default = "aes-gcm"
encryption_algorithm:value("xor",translate("xor"))
encryption_algorithm:value("chacha20",translate("chacha20"))
encryption_algorithm:value("aes-gcm",translate("aes-gcm"))
encryption_algorithm:value("aes-256-gcm",translate("aes-256-gcm"))
encryption_algorithm:value("openssl-aes-gcm",translate("openssl-aes-gcm"))
encryption_algorithm:value("openssl-chacha20",translate("openssl-chacha20"))
encryption_algorithm:value("openssl-aes-256-gcm",translate("openssl-aes-256-gcm"))
encryption_algorithm:depends("etcmd", "etcmd")
multi_thread = s:taboption("privacy", Flag, "multi_thread", translate("Enable Multithreading"),
translate("Enable multithreaded operation; single-threaded by default (--multi-thread parameter)"))
multi_thread:depends("etcmd", "etcmd")
multi_thread_count = s:taboption("privacy", Value, "multi_thread_count", translate("Number of Threads"),
translate("the number of threads to use, default is 2, only effective when multi-thread is enabled, must be greater than 2 (--multi-thread-count parameter)"))
multi_thread_count.placeholder = "2"
multi_thread_count:depends("etcmd", "etcmd")
disable_ipv6 = s:taboption("privacy", Flag, "disable_ipv6", translate("Disable IPv6"),
translate("Do not use IPv6 (--disable-ipv6 parameter)"))
disable_ipv6:depends("etcmd", "etcmd")
latency_first = s:taboption("privacy", Flag, "latency_first", translate("Enable Latency First"),
translate("Latency-first mode: attempts to forward traffic via the lowest latency path. "
.. "By default, the shortest path is used (--latency-first parameter)"))
latency_first:depends("etcmd", "etcmd")
comp = s:taboption("privacy", ListValue, "comp", translate("Compression Algorithm"),
translate("Compression algorithm to use (--compression parameter)"))
comp.default = "none"
comp:value("none",translate("default"))
comp:value("zstd",translate("zstd"))
comp:depends("etcmd", "etcmd")
exit_node = s:taboption("privacy", Flag, "exit_node", translate("Enable Exit Node"),
translate("Allow this node to act as an exit node (--enable-exit-node parameter)"))
exit_node:depends("etcmd", "etcmd")
exit_nodes = s:taboption("privacy", DynamicList, "exit_nodes", translate("Exit Node Addresses"),
translate("Exit nodes to forward all traffic through. These are virtual IPv4 addresses. "
.. "Priority is determined by the order in the list (--exit-nodes parameter)"))
exit_nodes:depends("etcmd", "etcmd")
smoltcp = s:taboption("privacy", Flag, "smoltcp", translate("Use Userspace TCP/IP Stack"),
translate("Enable smoltcp stack for subnet proxying (--use-smoltcp parameter)"))
smoltcp:depends("etcmd", "etcmd")
no_tun = s:taboption("privacy", Flag, "no_tun", translate("No TUN Mode"),
translate("Do not create a TUN device; subnet proxying can still be used to access nodes (--no-tun parameter)"))
no_tun:depends("etcmd", "etcmd")
proxy_forward = s:taboption("privacy", Flag, "proxy_forward", translate("Disable Built-in NAT"),
translate("Use system kernel to forward subnet proxy packets, disabling built-in NAT (--proxy-forward-by-system parameter)"))
proxy_forward:depends("etcmd", "etcmd")
manual_routes = s:taboption("privacy", DynamicList, "manual_routes", translate("Route CIDR"),
translate("Manually assign route CIDRs. This disables subnet proxying and WireGuard routes propagated from peer nodes "
.. "(--manual-routes parameter)"))
manual_routes.placeholder = "192.168.0.0/16"
manual_routes:depends("etcmd", "etcmd")
relay_network = s:taboption("privacy", Flag, "relay_network", translate("Forward Whitelisted Network Traffic"),
translate("Only forward traffic for whitelisted networks. By default, all networks are allowed"))
relay_network:depends("etcmd", "etcmd")
whitelist = s:taboption("privacy", DynamicList, "whitelist", translate("Whitelisted Networks"),
translate("Only forward traffic for whitelisted networks. Input is a wildcard string, "
.. "e.g., '*' (all networks), 'def*' (networks prefixed with 'def')<br>Multiple networks can be specified. "
.. "If empty, forwarding is disabled (--relay-network-whitelist parameter)"))
whitelist:depends("relay_network", "1")
socks_port = s:taboption("privacy", Value, "socks_port", translate("SOCKS5 Port"),
translate("Enable a SOCKS5 server to allow SOCKS5 clients to access the virtual network. "
.. "Leave blank to disable (--socks5 parameter)"))
socks_port.datatype = "range(1,65535)"
socks_port.placeholder = "1080"
socks_port:depends("etcmd", "etcmd")
disable_p2p = s:taboption("privacy", Flag, "disable_p2p", translate("Disable P2P"),
translate("Disable P2P communication; only use nodes specified by -p to forward packets (--disable-p2p parameter)"))
disable_p2p:depends("etcmd", "etcmd")
p2p_only = s:taboption("privacy", Flag, "p2p_only", translate("P2P only"),
translate("only communicate with peers that already establish p2p connection. (--p2p-only parameter)"))
p2p_only:depends("etcmd", "etcmd")
disable_udp = s:taboption("privacy", Flag, "disable_udp", translate("Disable UDP"),
translate("Disable UDP hole punching (--disable-udp-hole-punching parameter)"))
disable_udp:depends("etcmd", "etcmd")
udp_white_port = s:taboption("privacy", Value, "udp_white_port", translate("UDP whitelist"),
translate("udp port whitelist. Supports single ports (53) and ranges (5000-6000). (--udp-whitelist parameter)"))
udp_white_port:depends("etcmd", "etcmd")
disable_tcp = s:taboption("privacy", Flag, "disable_tcp", translate("Disable TCP"),
translate("Disable TCP hole punching (--disable-tcp-hole-punching parameter)"))
disable_tcp:depends("etcmd", "etcmd")
tcp_white_port = s:taboption("privacy", Value, "tcp_white_port", translate("TCP whitelist"),
translate("tcp port whitelist. Supports single ports (53) and ranges (5000-6000). (--tcp-whitelist parameter)"))
tcp_white_port:depends("etcmd", "etcmd")
disable_sym = s:taboption("privacy", Flag, "disable_sym", translate("Disable sym"),
translate("if true, disable udp nat hole punching for symmetric nat (NAT4), which is based on birthday attack and may be blocked by ISP. (--disable-sym-hole-punching parameter)"))
disable_sym:depends("etcmd", "etcmd")
relay_all = s:taboption("privacy", Flag, "relay_all", translate("Allow Forwarding"),
translate("Forward RPC packets from all peer nodes, even if they are not in the relay network whitelist.<br>"
.. "This can help peer nodes in non-whitelisted networks establish P2P connections. (--relay-all-peer-rpc parameter)"))
relay_all:depends("etcmd", "etcmd")
bind_device = s:taboption("privacy", Flag, "bind_device", translate("Bind to Physical NIC Only"),
translate("Bind the connector socket to the physical device to avoid routing issues.<br>"
.. "For example, if the subnet proxy segment conflicts with a peer node, "
.. "binding the physical NIC enables normal communication. (--bind-device parameter)"))
bind_device.default = "0"
bind_device:depends("etcmd", "etcmd")
kcp_proxy = s:taboption("privacy", Flag, "kcp_proxy", translate("Enable KCP Proxy"),
translate("Convert TCP traffic to KCP traffic to reduce latency and improve speed.<br>"
.. "All nodes in the virtual network must be using EasyTier version v2.2.0 or higher for this feature. "
.. "(--enable-kcp-proxy parameter)"))
kcp_proxy:depends("etcmd", "etcmd")
kcp_input = s:taboption("privacy", Flag, "kcp_input", translate("Disable KCP Input"),
translate("Disallow other nodes from using KCP proxy TCP streams to this node.<br>"
.. "KCP proxy-enabled nodes accessing this node will still use the original method. (--disable-kcp-input parameter)"))
kcp_input:depends("etcmd", "etcmd")
disable_relay_kcp = s:taboption("privacy", Flag, "disable_relay_kcp", translate("Disable relay kcp"),
translate("If true, disable relay kcp packets. avoid consuming too many bandwidth. default is false. (--disable-relay-kcp parameter)"))
disable_relay_kcp:depends("etcmd", "etcmd")
relay_kcp = s:taboption("privacy", Flag, "relay_kcp", translate("Relay foreign network kcp"),
translate("If true, allow relay kcp packets from foreign network. default is false (not forward foreign network kcp packets). (--enable-relay-foreign-network-kcp parameter)"))
relay_kcp:depends("etcmd", "etcmd")
quic_proxy = s:taboption("privacy", Flag, "quic_proxy", translate("Enable QUIC Proxy"),
translate("Proxy tcp streams with QUIC, improving the latency and throughput on the network with udp packet loss.<br>"
.. "All nodes in the virtual network must be using EasyTier version v2.3.2 or higher for this feature. "
.. "(--enable-quic-proxy parameter)"))
quic_proxy:depends("etcmd", "etcmd")
quic_input = s:taboption("privacy", Flag, "quic_input", translate("Disable QUIC Input"),
translate("Do not allow other nodes to use QUIC to proxy tcp streams to this node.") ..
translate("When a node with QUIC proxy enabled accesses this node, the original tcp connection is preserved.") ..
translate("<br>QUIC proxy-enabled nodes accessing this node will still use the original method. (--disable-quic-input parameter)"))
quic_input:depends("etcmd", "etcmd")
port_forward = s:taboption("privacy", DynamicList, "port_forward", translate("Port Forwarding"),
translate("Forward a local port to a remote port within the virtual network.<br>"
.. "Example: udp://0.0.0.0:12345/10.126.126.1:23456 means forwarding local UDP port 12345 to 10.126.126.1:23456 "
.. "in the virtual network.<br>Multiple entries can be specified. (--port-forward parameter)"))
port_forward:depends("etcmd", "etcmd")
accept_dns = s:taboption("privacy", Flag, "accept_dns", translate("Enable Magic DNS"),
translate("With Magic DNS, you can access other nodes using domain names, e.g., <hostname>.et.net. "
.. "Magic DNS will modify your system DNS settings, please enable with caution. (--accept-dns parameter)"))
accept_dns:depends("etcmd", "etcmd")
private_mode = s:taboption("privacy", Flag, "private_mode", translate("Enable Private Mode"),
translate("When enabled, nodes with a different network name and password are not allowed to handshake or "
.. "relay via this node. (--private-mode parameter)"))
private_mode:depends("etcmd", "etcmd")
foreign_relay_bps_limit = s:taboption("privacy", Value, "foreign_relay_bps_limit", translate("Forwarding Rate"),
translate("the maximum bps limit for foreign network relay, default is no limit. unit: BPS (bytes per second). "
.. "(--foreign-relay-bps-limit parameter)"))
foreign_relay_bps_limit:depends("etcmd", "etcmd")
extra_args = s:taboption("privacy", Value, "extra_args", translate("Extra Parameters"),
translate("Additional command-line arguments passed to the backend process"))
extra_args.placeholder = "--tcp-whitelist 80 --udp-whitelist 53"
extra_args:depends("etcmd", "etcmd")
log = s:taboption("general", ListValue, "log", translate("Program Log"),
translate("Runtime log is located at /tmp/easytier.log. View it in the log section above.<br>"
.. "Levels: Error < Warning < Info < Debug < Trace"))
log.default = "off"
log:value("off", translate("Off"))
log:value("error", translate("Error"))
log:value("warn", translate("Warning"))
log:value("info", translate("Info"))
log:value("debug", translate("Debug"))
log:value("trace", translate("Trace"))
-- Network Configuration Options
auto_config_interface = s:taboption("privacy", Flag, "auto_config_interface", translate("Auto Configure Interface"),
translate("Automatically create and configure the EasyTier network interface"))
auto_config_interface.default = "1"
interface_netmask = s:taboption("privacy", Value, "interface_netmask", translate("Interface Netmask"),
translate("Subnet mask for the EasyTier interface (default: 255.0.0.0)"))
interface_netmask.placeholder = "255.0.0.0"
interface_netmask.default = "255.0.0.0"
interface_netmask.datatype = "ip4addr"
interface_netmask:depends("auto_config_interface", "1")
auto_config_firewall = s:taboption("privacy", Flag, "auto_config_firewall", translate("Auto Configure Firewall"),
translate("Automatically add and manage firewall rules"))
auto_config_firewall.default = "1"
et_forward = s:taboption("privacy", MultiValue, "et_forward", translate("Access Control"),
translate("Set traffic permission rules between different network zones"))
et_forward:value("etfwlan", translate("Allow traffic from EasyTier virtual network to LAN"))
et_forward:value("etfwwan", translate("Allow traffic from EasyTier virtual network to WAN"))
et_forward:value("lanfwet", translate("Allow traffic from LAN to EasyTier virtual network"))
et_forward:value("wanfwet", translate("Allow traffic from WAN to EasyTier virtual network"))
et_forward.rmempty = true
et_forward.optional = true
et_forward:depends("auto_config_firewall", "1")
check = s:taboption("privacy", Flag, "check", translate("Connectivity Check"),
translate("Enable connectivity check to specify remote device IPs; if all specified IPs fail to ping, "
.. "the EasyTier program will restart."))
checkip = s:taboption("privacy", DynamicList, "checkip", translate("Check IPs"),
translate("Make sure the remote device IPs entered here are correct and reachable; "
.. "incorrect entries may cause ping failures and repeated program restarts."))
checkip.rmempty = true
checkip.datatype = "ip4addr"
checkip:depends("check", "1")
checktime = s:taboption("privacy", ListValue, "checktime", translate("Interval Time (minutes)"),
translate("Interval time for checking connectivity; how often the specified IPs are pinged."))
for s = 1, 60 do
checktime:value(s)
end
checktime:depends("check", "1")
return m
@@ -0,0 +1,19 @@
local http = luci.http
m = Map("easytier", translate("EasyTier Status"))
m.description = translate("A simple, secure, decentralized VPN solution for intranet penetration, implemented in Rust using the Tokio framework. "
.. "Project URL: <a href=\"https://github.com/EasyTier/EasyTier\" target=\"_blank\">github.com/EasyTier/EasyTier</a>&nbsp;&nbsp;"
.. "<a href=\"http://easytier.cn\" target=\"_blank\">Official Documentation</a>&nbsp;&nbsp;"
.. "<a href=\"http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=jhP2Z4UsEZ8wvfGPLrs0VwLKn_uz0Q_p&authKey=OGKSQLfg61YPCpVQuvx%2BxE7hUKBVBEVi9PljrDKbHlle6xqOXx8sOwPPTncMambK&noverify=0&group_code=949700262\" target=\"_blank\">QQ Group</a>&nbsp;&nbsp;")
.. "<a href=\"https://github.com/EasyTier/EasyTier/releases\" target=\"_blank\"><img alt=\"\" src=\"https://img.shields.io/github/v/tag/EasyTier/EasyTier?include_prereleases&logo=github&label=tag&link=https%3A%2F%2Fgithub.com%2FEasyTier%2FEasyTier%2Freleases\" style=\"vertical-align:middle;\"></a>&nbsp;"
.. "<a href=\"https://github.com/EasyTier/EasyTier/releases\" target=\"_blank\"><img alt=\"\" src=\"https://img.shields.io/github/v/release/EasyTier/EasyTier?logo=github&link=https%3A%2F%2Fgithub.com%2FEasyTier%2FEasyTier%2Freleases\" style=\"vertical-align:middle;\"></a>&nbsp;"
.. "<a href=\"https://github.com/EasyTier/luci-app-easytier/releases\" target=\"_blank\"><img alt=\"\" src=\"https://img.shields.io/github/v/release/EasyTier/luci-app-easytier?logo=openwrt&label=luci&link=https%3A%2F%2Fgithub.com%2FEasyTier%2Fluci-app-easytier%2Freleases\" style=\"vertical-align:middle;\"></a>"
m.pageaction = false
-- 状态卡片
m:section(SimpleSection).template = "easytier/easytier_status"
-- 连接信息卡片
m:section(SimpleSection).template = "easytier/easytier_cli"
return m
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,545 @@
<%
local util = require "luci.util"
local i18n = require "luci.i18n"
local translate = i18n.translate
-%>
<%+header%>
<style>
:root {
--log-bg: #ffffff;
--log-text: #333333;
--log-border: #e0e0e0;
--log-header-bg: #f5f5f5;
--log-info: #2196F3;
--log-warn: #FF9800;
--log-error: #F44336;
--log-hover: #f0f0f0;
--tab-active: #2196F3;
--shadow: rgba(0,0,0,0.1);
}
@media (prefers-color-scheme: dark) {
:root {
--log-bg: #1e1e1e;
--log-text: #d4d4d4;
--log-border: #3e3e3e;
--log-header-bg: #252525;
--log-info: #4FC3F7;
--log-warn: #FFB74D;
--log-error: #EF5350;
--log-hover: #2a2a2a;
--tab-active: #42A5F5;
--shadow: rgba(0,0,0,0.3);
}
}
.log-container {
background: var(--log-bg);
border-radius: 12px;
overflow: hidden;
margin: 16px 0;
box-shadow: 0 4px 12px var(--shadow);
border: 1px solid var(--log-border);
transition: box-shadow 0.3s ease;
}
.log-container:hover {
box-shadow: 0 6px 16px var(--shadow);
}
.log-header {
background: var(--log-header-bg);
padding: 16px 20px;
border-bottom: 1px solid var(--log-border);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.log-tabs {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.log-tab {
padding: 10px 20px;
border: none;
background: transparent;
color: #757575;
cursor: pointer;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.log-tab:hover {
background: var(--log-hover);
transform: translateY(-2px);
}
.log-tab.active {
color: var(--tab-active);
background: var(--log-hover);
}
.log-tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 10%;
right: 10%;
height: 3px;
background: var(--tab-active);
border-radius: 3px 3px 0 0;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
transform: scaleX(0);
opacity: 0;
}
to {
transform: scaleX(1);
opacity: 1;
}
}
.log-controls {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.log-select {
padding: 8px 14px;
border: 1px solid var(--log-border);
background: var(--log-bg);
color: var(--log-text);
border-radius: 8px;
cursor: pointer;
font-size: 13px;
transition: all 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.log-select:hover, .log-select:focus {
outline: none;
border-color: var(--tab-active);
box-shadow: 0 2px 8px rgba(33,150,243,0.2);
transform: translateY(-1px);
}
.log-btn {
padding: 8px 16px;
border: 1px solid var(--log-border);
background: var(--log-bg);
color: var(--log-text);
border-radius: 8px;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.log-btn:hover {
background: var(--log-hover);
border-color: var(--tab-active);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.log-btn:active {
transform: translateY(0);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.log-btn-primary {
background: var(--tab-active);
color: white;
border-color: var(--tab-active);
}
.log-btn-primary:hover {
opacity: 0.9;
box-shadow: 0 4px 12px rgba(33,150,243,0.3);
}
.log-content {
background: var(--log-bg);
color: var(--log-text);
padding: 20px;
height: 500px;
overflow-y: auto;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 13px;
line-height: 1.8;
white-space: pre-wrap;
word-wrap: break-word;
}
.log-line {
margin: 2px 0;
padding: 4px 10px;
border-radius: 0;
transition: background 0.15s ease;
}
.log-line:hover {
background: var(--log-hover);
}
.log-level-INFO { color: var(--log-info); }
.log-level-WARN { color: var(--log-warn); }
.log-level-ERROR { color: var(--log-error); }
.log-level-DEBUG { color: #9E9E9E; }
.log-level-TRACE { color: #BDBDBD; }
.log-timestamp {
color: #9E9E9E;
margin-right: 12px;
font-weight: 600;
}
.log-empty {
text-align: center;
color: #9E9E9E;
padding: 60px 20px;
font-style: italic;
font-size: 14px;
}
.log-footer {
background: var(--log-header-bg);
padding: 8px 20px;
border-top: 1px solid var(--log-border);
font-size: 12px;
color: #757575;
text-align: right;
}
.log-size-1 { color: #4CAF50; }
.log-size-2 { color: #FF9800; }
.log-size-3 { color: #FF5722; }
.log-size-4 { color: #F44336; }
.log-size-5 { color: #D32F2F; font-weight: bold; }
.log-content::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.log-content::-webkit-scrollbar-track {
background: var(--log-header-bg);
border-radius: 5px;
}
.log-content::-webkit-scrollbar-thumb {
background: var(--log-border);
border-radius: 5px;
transition: background 0.3s ease;
}
.log-content::-webkit-scrollbar-thumb:hover {
background: #757575;
}
@media (max-width: 768px) {
.log-header {
flex-direction: column;
align-items: stretch;
padding: 12px 16px;
}
.log-tabs {
width: 100%;
justify-content: space-around;
}
.log-controls {
width: 100%;
justify-content: space-between;
}
.log-content {
height: 400px;
font-size: 12px;
padding: 16px;
}
.log-btn {
flex: 1;
text-align: center;
}
}
</style>
<div class="log-container">
<div class="log-header">
<div class="log-tabs">
<button class="log-tab active" data-log="core" onclick="switchLog('core')">
<%=translate("core log")%>
</button>
<button class="log-tab" data-log="web" onclick="switchLog('web')">
<%=translate("web log")%>
</button>
</div>
<div class="log-controls">
<a href="https://deepwiki.com/EasyTier/EasyTier" target="_blank" style="display:inline-block;height:36px;line-height:36px;margin-right:8px;">
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki" style="vertical-align:middle;">
</a>
<select id="log_level_filter" class="log-select" onchange="filterLogLevel()">
<option value="all"><%=translate("All Levels")%></option>
<option value="INFO">INFO</option>
<option value="WARN">WARN</option>
<option value="ERROR">ERROR</option>
<option value="DEBUG">DEBUG</option>
<option value="TRACE">TRACE</option>
</select>
<button class="log-btn log-btn-primary" id="auto_refresh_btn" onclick="toggleAutoRefresh()">
<%=translate("Stop Auto Refresh")%>
</button>
<button class="log-btn" onclick="clearCurrentLog()"><%=translate("Clear Logs")%></button>
<button class="log-btn" onclick="downloadCurrentLog()"><%=translate("Download Log")%></button>
</div>
</div>
<div class="log-content" id="log_content">
<div class="log-empty"><%=translate("Loading logs...")%></div>
</div>
<div class="log-footer" id="log_footer">
<span id="log_size_info"></span>
</div>
</div>
<script type="text/javascript">
//<![CDATA[
var currentLog = 'core';
var pollTimer = null;
var allLogLines = [];
var autoRefreshEnabled = true;
function filterLogLevel() {
var filter = document.getElementById('log_level_filter').value;
var content = document.getElementById('log_content');
var lines = content.querySelectorAll('.log-line');
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (filter === 'all') {
line.style.display = '';
} else {
var hasLevel = false;
for (var j = 0; j < line.classList.length; j++) {
if (line.classList[j] === 'log-level-' + filter) {
hasLevel = true;
break;
}
}
line.style.display = hasLevel ? '' : 'none';
}
}
}
function switchLog(type) {
currentLog = type;
var tabs = document.querySelectorAll('.log-tab');
for (var i = 0; i < tabs.length; i++) {
if (tabs[i].getAttribute('data-log') === type) {
tabs[i].classList.add('active');
} else {
tabs[i].classList.remove('active');
}
}
document.getElementById('log_size_info').innerHTML = '';
refreshLog();
}
function formatLogLine(line) {
if (!line || line.trim() === '') return '';
var timestamp = '';
var content = line.trim();
var level = 'INFO';
// 匹配 ISO 8601 格式: 2026-04-14T20:30:54.744803154+08:00
var isoTimeMatch = content.match(/^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})\.\d+[+-]\d{2}:\d{2}\s*/);
if (isoTimeMatch) {
timestamp = isoTimeMatch[1] + ' ' + isoTimeMatch[2];
content = content.substring(isoTimeMatch[0].length);
} else {
// 匹配简单格式: 2026-04-14 20:19:33
var simpleTimeMatch = content.match(/^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})[:\s]*/);
if (simpleTimeMatch) {
timestamp = simpleTimeMatch[1];
content = content.substring(simpleTimeMatch[0].length);
}
}
// 匹配日志级别
var levelMatch = content.match(/^\s*(INFO|WARN|ERROR|DEBUG|TRACE)\s+/i);
if (levelMatch) {
level = levelMatch[1].toUpperCase();
content = content.substring(levelMatch[0].length);
}
var html = '<div class="log-line log-level-' + level + '">';
if (timestamp) {
html += '<span class="log-timestamp">' + escapeHtml(timestamp) + '</span>';
}
html += '<span class="log-level-' + level + '">' + escapeHtml(content) + '</span>';
html += '</div>';
return html;
}
function escapeHtml(text) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(text));
return div.innerHTML;
}
function refreshLog() {
var url = currentLog === 'core'
? '<%=url("admin/vpn/easytier/get_log")%>'
: '<%=url("admin/vpn/easytier/get_wlog")%>';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var content = document.getElementById('log_content');
var rawLog = xhr.responseText || '';
if (rawLog.trim() === '') {
content.innerHTML = '<div class="log-empty"><%=translate("No logs available")%></div>';
return;
}
var lines = rawLog.split('\n');
var html = '';
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (!line.trim()) continue;
html += formatLogLine(line);
}
content.innerHTML = html;
filterLogLevel();
if (autoRefreshEnabled) {
content.scrollTop = content.scrollHeight;
updateLogSize();
}
}
};
xhr.send();
}
function updateLogSize() {
var url = currentLog === 'core'
? '<%=url("admin/vpn/easytier/get_log_size")%>'
: '<%=url("admin/vpn/easytier/get_wlog_size")%>';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
var sizeMB = (data.size / 1024 / 1024).toFixed(2);
var logPath = currentLog === 'core' ? '/tmp/easytier.log' : '/tmp/easytierweb.log';
var sizeClass = '';
if (sizeMB < 1) sizeClass = 'log-size-1';
else if (sizeMB < 2) sizeClass = 'log-size-2';
else if (sizeMB < 3) sizeClass = 'log-size-3';
else if (sizeMB < 4) sizeClass = 'log-size-4';
else sizeClass = 'log-size-5';
document.getElementById('log_size_info').innerHTML =
logPath + ': <span class="' + sizeClass + '">' + sizeMB + ' MB</span>';
}
};
xhr.send();
}
function clearCurrentLog() {
var url = currentLog === 'core'
? '<%=url("admin/vpn/easytier/clear_log")%>'
: '<%=url("admin/vpn/easytier/clear_wlog")%>';
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
document.getElementById('log_content').innerHTML = '<div class="log-empty"><%=translate("Logs cleared")%></div>';
}
};
xhr.send();
}
function downloadCurrentLog() {
var content = document.getElementById('log_content');
var logText = '';
var lines = content.querySelectorAll('.log-line');
for (var i = 0; i < lines.length; i++) {
logText += lines[i].innerText + '\n';
}
if (logText.trim() === '') {
logText = content.innerText;
}
var blob = new Blob([logText], {type: 'text/plain;charset=utf-8'});
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'easytier-' + currentLog + '-log.txt';
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
function startPolling() {
if (pollTimer) clearInterval(pollTimer);
pollTimer = setInterval(function() {
if (autoRefreshEnabled) {
refreshLog();
}
}, 2000);
}
function toggleAutoRefresh() {
autoRefreshEnabled = !autoRefreshEnabled;
var btn = document.getElementById('auto_refresh_btn');
if (autoRefreshEnabled) {
btn.textContent = '<%=translate("Stop Auto Refresh")%>';
btn.classList.add('log-btn-primary');
} else {
btn.textContent = '<%=translate("Start Auto Refresh")%>';
btn.classList.remove('log-btn-primary');
}
}
refreshLog();
startPolling();
window.addEventListener('beforeunload', function() {
if (pollTimer) clearInterval(pollTimer);
});
//]]>
</script>
<%+footer%>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+1
View File
@@ -0,0 +1 @@
./zh_Hans
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,7 @@
config easytier
option enabled '0'
option easytierbin '/usr/bin/easytier-core'
option auto_config_interface '1'
option auto_config_firewall '1'
option interface_netmask '255.0.0.0'
@@ -0,0 +1,15 @@
instance_name = "default"
instance_id = "f9f05421-7c6b-47d6-a67d-4d58d49f51d8"
dhcp = true
listeners = []
exit_nodes = []
rpc_portal = "0.0.0.0:15888"
[network_identity]
network_name = "mynetwork"
network_secret = "mysecret"
[[peer]]
uri = "tcp://public.easytier.top:11010"
@@ -0,0 +1,894 @@
#!/bin/sh /etc/rc.common
# Copyright (C) 2008-2020 OpenWrt.org
START=99
USE_PROCD=1
# 引入模块化脚本
EASYTIER_LIB_DIR="/usr/share/easytier"
[ -f "${EASYTIER_LIB_DIR}/utils.sh" ] && . "${EASYTIER_LIB_DIR}/utils.sh"
[ -f "${EASYTIER_LIB_DIR}/firewall.sh" ] && . "${EASYTIER_LIB_DIR}/firewall.sh"
[ -f "${EASYTIER_LIB_DIR}/download.sh" ] && . "${EASYTIER_LIB_DIR}/download.sh"
check() {
if [ ! -z "$checkip" ]; then
echo ' ' >/tmp/easytier_check
cat > /tmp/easytier_check <<'EOF'
#!/bin/sh
time="$(($(uci -q get easytier.@easytier[0].checktime) * 60))"
if [ -z "$time" ]; then
time=120
uci -q set easytier.@easytier[0].checktime=2
uci commit easytier
fi
while true; do
if [ "$(uci -q get easytier.@easytier[0].enabled)" = "1" ]; then
/bin/ping -c 3 223.5.5.5 -w 5 >/dev/null 2>&1
if [ "$?" == "0" ]; then
if [ ! -z "$(uci -q get easytier.@easytier[0].checkip)" ]; then
online=""
for ip in $(uci -q get easytier.@easytier[0].checkip); do
ms=`echo $(/bin/ping -4 $ip -c 2 -w 4 -q) | awk -F '/' '{print $4}'`
[ ! -z "$ms" ] && echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 通断检测:${ip} 延迟:${ms}ms " >>/tmp/easytier.log && online=1
[ -z "$ms" ] && echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 通断检测:${ip} 网络中断 " >>/tmp/easytier.log
done
[ "$online" != "1" ] && echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 通断检测:$(uci -q get easytier.@easytier[0].checkip) 所有指定IP皆无法ping通,重新启动程序! " >>/tmp/easytier.log && /etc/init.d/easytier restart
fi
else
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 通断检测:检测到互联网未能成功访问,跳过检测 " >>/tmp/easytier.log
fi
else
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 通断检测:程序已关闭,退出检测 " >>/tmp/easytier.log
/etc/init.d/easytier restart
break
fi
sleep $((time))
[ "$(du -k /tmp/easytier.log | cut -f1)" = "1000" ] && echo " " >/tmp/easytier.log
done
EOF
chmod +x /tmp/easytier_check
procd_open_instance
procd_set_param respawn
procd_set_param command /tmp/easytier_check
procd_close_instance
fi
}
# get_tz 函数已移至 utils.sh,保留兼容性包装
# 如果 utils.sh 未加载,使用内联定义
if ! type get_tz >/dev/null 2>&1; then
get_tz() {
SET_TZ=""
[ -e "/etc/localtime" ] && return
for tzfile in /etc/TZ /var/etc/TZ
do
[ -e "$tzfile" ] || continue
tz="$(cat $tzfile 2>/dev/null)"
done
[ -z "$tz" ] && return
SET_TZ=$tz
}
fi
check_bin() {
# 优先使用 UCI 配置的代理列表(list 类型),否则使用默认值
local proxys=""
local proxy_list=$(uci -q get easytier.@easytier[0].github_proxys)
# 判断是否有多个代理
if [ ! -z "$proxy_list" ]; then
if [[ "$(grep "list github_proxys" /etc/config/easytier | awk '{print $3}' | wc -l)" -eq 1 ]]; then
proxys="$proxy_list"
else
proxys=$(cat /etc/config/easytier | grep 'list github_proxys' | awk -F 'list github_proxys' '{print $2}' | sed "s/'/\n/g" | tr -d " ' ")
fi
fi
# 如果没有配置代理,使用默认值
if [ -z "$proxys" ]; then
proxys="https://ghproxy.net/ https://gh-proxy.com/ https://cdn.gh-proxy.com/ https://ghfast.top/"
fi
curltest=`which curl`
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
if [ -z "$curltest" ] || [ ! -s "`which curl`" ] ; then
tag="$( wget --no-check-certificate -T 5 -t 3 --user-agent "$user_agent" --max-redirect=0 --output-document=- https://api.github.com/repos/EasyTier/EasyTier/releases/latest 2>&1 | grep 'tag_name' | cut -d\" -f4 )"
[ -z "$tag" ] && tag="$( wget --no-check-certificate -T 5 -t 3 --user-agent "$user_agent" --quiet --output-document=- https://api.github.com/repos/EasyTier/EasyTier/releases/latest 2>&1 | grep 'tag_name' | cut -d\" -f4 )"
else
tag="$( curl -k --connect-timeout 3 --user-agent "$user_agent" https://api.github.com/repos/EasyTier/EasyTier/releases/latest 2>&1 | grep 'tag_name' | cut -d\" -f4 )"
[ -z "$tag" ] && tag="$( curl -Lk --connect-timeout 3 --user-agent "$user_agent" -s https://api.github.com/repos/EasyTier/EasyTier/releases/latest 2>&1 | grep 'tag_name' | cut -d\" -f4 )"
fi
# 如果获取失败,从 UCI 配置或使用默认版本
if [ -z "$tag" ]; then
tag=$(uci -q get easytier.@easytier[0].fallback_version)
[ -z "$tag" ] && tag="v2.6.2"
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 开始在线下载${tag}版本,${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip下载较慢耐心等候" >>/tmp/easytier.log
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 开始在线下载${tag}版本,${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip下载较慢耐心等候" >>/tmp/easytierweb.log
mkdir -p "$path"
for proxy in $proxys ; do
if curl -L -k -o /tmp/easytier.zip --connect-timeout 10 --retry 3 "${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip" || \
wget --no-check-certificate --timeout=10 --tries=3 -O /tmp/easytier.zip "${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip" ; then
unzip -j -q -o /tmp/easytier.zip -d /tmp
chmod +x /tmp/easytier-core /tmp/easytier-cli /tmp/easytier-web /tmp/easytier-web-embed || true
if [ "$(uci -q get easytier.@easytier[0].enabled)" = "1" ] ; then
mv -f /tmp/easytier-core ${path}/
mv -f /tmp/easytier-cli ${path}/
chmod +x "$easytierbin"
if [ "$($easytierbin -h 2>&1 | wc -l)" -gt 3 ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : ${easytierbin} 下载成功" >>/tmp/easytier.log
else
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : ${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip 下载不完整,请手动下载上传程序" >>/tmp/easytier.log
fi
fi
if [ "$(uci -q get easytier.@easytier[0].web_enabled)" = "1" ] ; then
mv -f /tmp/easytier-web-embed ${webbin} || true
chmod +x "$webbin"
if [ "$($webbin -h 2>&1 | wc -l)" -gt 3 ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : ${webbin} 下载成功" >>/tmp/easytierweb.log
else
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : ${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip 下载不完整,请手动下载上传程序" >>/tmp/easytierweb.log
fi
fi
rm -rf /tmp/easytier.zip
break
else
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : ${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip 下载失败,请手动下载上传程序" >>/tmp/easytier.log
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : ${proxy}https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip 下载失败,请手动下载上传程序" >>/tmp/easytierweb.log
fi
done
}
check_config() {
config_file="/etc/easytier/config.toml"
instance_name=$(cat $config_file | grep "instance_name =" | awk '{print $3}'| tr -d '" ')
socks_port=$(cat $config_file | grep "socks5_proxy" | awk -F ":" '{print $3}'| tr -d '" ')
[ -z "$(grep -F 'listeners = []' $config_file)" ] && listenermode="ON"
tcp_port=$(grep -oE '"tcp://(\[::\]|0\.0\.0\.0):[0-9]+"' "$config_file" | head -n1 | sed -E 's/.*:([0-9]+)"/\1/')
udp_port=$(grep -oE '"udp://(\[::\]|0\.0\.0\.0):[0-9]+"' "$config_file" | head -n1 | sed -E 's/.*:([0-9]+)"/\1/')
ws_port=$(grep -oE '"ws://(\[::\]|0\.0\.0\.0):[0-9]+"' "$config_file" | head -n1 | sed -E 's/.*:([0-9]+)"/\1/')
wss_port=$(grep -oE '"wss://(\[::\]|0\.0\.0\.0):[0-9]+"' "$config_file" | head -n1 | sed -E 's/.*:([0-9]+)"/\1/')
wg_port=$(grep -oE '"wg://(\[::\]|0\.0\.0\.0):[0-9]+"' "$config_file" | head -n1 | sed -E 's/.*:([0-9]+)"/\1/')
quic_port=$(grep -oE '"quic://(\[::\]|0\.0\.0\.0):[0-9]+"' "$config_file" | head -n1 | sed -E 's/.*:([0-9]+)"/\1/')
tunname=$(grep "dev_name =" "$config_file" | awk '{print $3}' | tr -d "'\", ")
proxy_network=$(grep -F '[[proxy_network]]' $config_file)
wireguard_port=$(cat $config_file | grep "wireguard_listen = " | awk -F ":" '{print $2}'| tr -d '/", ')
log="$(uci -q get easytier.@easytier[0].log || echo off)"
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 检测配置文件可用性 ${easytierbin} --check-config --config-file ${config_file}" >>/tmp/easytier.log
${easytierbin} --check-config --config-file ${config_file} >> /tmp/easytier.log 2>&1 &
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 开始运行 ${easytierbin} -c ${config_file}" >>/tmp/easytier.log
procd_open_instance
get_tz
[ -z "$SET_TZ" ] || procd_set_param env TZ="$SET_TZ"
procd_set_param command /bin/sh -c "
cd /tmp ; ${easytierbin} -c ${config_file} --console-log-level ${log} >> /tmp/easytier.log 2>&1 &
ET_CORE_PID=\$!
{
while true; do
LOG_SIZE=\$(ls -l /tmp/easytier.log | awk '{print int(\$5/1024)}')
if [ \$LOG_SIZE -gt 5120 ]; then
tail -n 500 /tmp/easytier.log > /tmp/easytier.log.tmp
mv /tmp/easytier.log.tmp /tmp/easytier.log
fi
sleep 300
done
} &
LOG_MANAGEMENT_PID=\$!
trap \"kill -TERM \$ET_CORE_PID; kill -TERM \$LOG_MANAGEMENT_PID; exit\" SIGINT SIGTERM EXIT
wait \$ET_CORE_PID
"
}
get_etconfig() {
web_config="$(uci -q get easytier.@easytier[0].web_config)"
network_name="$(uci -q get easytier.@easytier[0].network_name)"
network_secret="$(uci -q get easytier.@easytier[0].network_secret)"
ip_dhcp="$(uci -q get easytier.@easytier[0].ip_dhcp || echo 0)"
ipaddr="$(uci -q get easytier.@easytier[0].ipaddr)"
ip6addr="$(uci -q get easytier.@easytier[0].ip6addr)"
external_node="$(uci -q get easytier.@easytier[0].external_node)"
proxy_network="$(uci -q get easytier.@easytier[0].proxy_network)"
rpc_portal="$(uci -q get easytier.@easytier[0].rpc_portal || echo 15888)"
rpc_portal_whitelist="$(uci -q get easytier.@easytier[0].rpc_portal_whitelist)"
listenermode="$(uci -q get easytier.@easytier[0].listenermode)"
tunname="$(uci -q get easytier.@easytier[0].tunname)"
relay_network="$(uci -q get easytier.@easytier[0].relay_network || echo 0)"
tcp_port="$(uci -q get easytier.@easytier[0].tcp_port | awk '{print $1}')"
ws_port="$(uci -q get easytier.@easytier[0].ws_port)"
wss_port="$(uci -q get easytier.@easytier[0].wss_port)"
wg_port="$(uci -q get easytier.@easytier[0].wg_port)"
quic_port="$(uci -q get easytier.@easytier[0].quic_port)"
peeradd="$(uci -q get easytier.@easytier[0].peeradd)"
desvice_name="$(uci -q get easytier.@easytier[0].desvice_name)"
[ -z "$desvice_name" ] || desvice_name=$(echo "$desvice_name" | sed 's/[\\$`"()|><&;]/\\&/g')
instance_name="$(uci -q get easytier.@easytier[0].instance_name)"
vpn_portal="$(uci -q get easytier.@easytier[0].vpn_portal)"
mtu="$(uci -q get easytier.@easytier[0].mtu)"
default_protocol="$(uci -q get easytier.@easytier[0].default_protocol)"
disable_encryption="$(uci -q get easytier.@easytier[0].disable_encryption || echo 0)"
whitelist="$(uci -q get easytier.@easytier[0].whitelist)"
multi_thread="$(uci -q get easytier.@easytier[0].multi_thread || echo 0)"
multi_thread_count="$(uci -q get easytier.@easytier[0].multi_thread_count)"
disable_ipv6="$(uci -q get easytier.@easytier[0].disable_ipv6 || echo 0)"
latency_first="$(uci -q get easytier.@easytier[0].latency_first || echo 0)"
exit_node="$(uci -q get easytier.@easytier[0].exit_node || echo 0)"
smoltcp="$(uci -q get easytier.@easytier[0].smoltcp || echo 0)"
exit_nodes="$(uci -q get easytier.@easytier[0].exit_nodes || echo 0)"
no_tun="$(uci -q get easytier.@easytier[0].no_tun || echo 0)"
manual_routes="$(uci -q get easytier.@easytier[0].manual_routes)"
log="$(uci -q get easytier.@easytier[0].log || echo off)"
check="$(uci -q get easytier.@easytier[0].check || echo 0)"
disable_p2p="$(uci -q get easytier.@easytier[0].disable_p2p || echo 0)"
disable_udp="$(uci -q get easytier.@easytier[0].disable_udp || echo 0)"
relay_all="$(uci -q get easytier.@easytier[0].relay_all || echo 0)"
checkip="$(uci -q get easytier.@easytier[0].checkip)"
socks_port="$(uci -q get easytier.@easytier[0].socks_port)"
comp="$(uci -q get easytier.@easytier[0].comp || echo none)"
mapped_listeners="$(uci -q get easytier.@easytier[0].mapped_listeners)"
bind_device="$(uci -q get easytier.@easytier[0].bind_device || echo 0)"
kcp_proxy="$(uci -q get easytier.@easytier[0].kcp_proxy || echo 0)"
kcp_input="$(uci -q get easytier.@easytier[0].kcp_input || echo 0)"
proxy_forward="$(uci -q get easytier.@easytier[0].proxy_forward || echo 0)"
port_forward="$(uci -q get easytier.@easytier[0].port_forward)"
accept_dns="$(uci -q get easytier.@easytier[0].accept_dns || echo 0)"
private_mode="$(uci -q get easytier.@easytier[0].private_mode || echo 0)"
quic_proxy="$(uci -q get easytier.@easytier[0].quic_proxy || echo 0)"
quic_input="$(uci -q get easytier.@easytier[0].quic_input || echo 0)"
foreign_relay_bps_limit="$(uci -q get easytier.@easytier[0].foreign_relay_bps_limit)"
extra_args="$(uci -q get easytier.@easytier[0].extra_args)"
encryption_algorithm="$(uci -q get easytier.@easytier[0].encryption_algorithm)"
p2p_only="$(uci -q get easytier.@easytier[0].p2p_only || echo 0)"
disable_tcp="$(uci -q get easytier.@easytier[0].disable_tcp || echo 0)"
disable_sym="$(uci -q get easytier.@easytier[0].disable_sym || echo 0)"
udp_white_port="$(uci -q get easytier.@easytier[0].udp_white_port)"
tcp_white_port="$(uci -q get easytier.@easytier[0].tcp_white_port)"
disable_relay_kcp="$(uci -q get easytier.@easytier[0].disable_relay_kcp || echo 0)"
relay_kcp="$(uci -q get easytier.@easytier[0].relay_kcp || echo 0)"
auto_config_interface="$(uci -q get easytier.@easytier[0].auto_config_interface || echo 1)"
interface_netmask="$(uci -q get easytier.@easytier[0].interface_netmask)"
auto_config_firewall="$(uci -q get easytier.@easytier[0].auto_config_firewall || echo 1)"
}
set_firewall() {
# Check if auto_config_firewall is enabled
if [ "$auto_config_firewall" != "1" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 自动防火墙配置已禁用,跳过配置" >>/tmp/easytier.log
return
fi
if [ ! -z "$tcp_port" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_tcp_udp 放行端口 ${tcp_port} " >>/tmp/easytier.log
uci -q delete firewall.easytier_tcp_udp
uci set firewall.easytier_tcp_udp=rule
uci set firewall.easytier_tcp_udp.name="easytier_tcp_udp"
uci set firewall.easytier_tcp_udp.target="ACCEPT"
uci set firewall.easytier_tcp_udp.src="wan"
uci set firewall.easytier_tcp_udp.proto="tcp udp"
uci set firewall.easytier_tcp_udp.dest_port="$tcp_port"
uci set firewall.easytier_tcp_udp.enabled="1"
fi
if [ ! -z "$udp_port" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_udp 放行UDP端口 ${udp_port} " >>/tmp/easytier.log
uci -q delete firewall.easytier_udp
uci set firewall.easytier_udp=rule
uci set firewall.easytier_udp.name="easytier_udp"
uci set firewall.easytier_udp.target="ACCEPT"
uci set firewall.easytier_udp.src="wan"
uci set firewall.easytier_udp.proto="udp"
uci set firewall.easytier_udp.dest_port="$udp_port"
uci set firewall.easytier_udp.enabled="1"
fi
if [ ! -z "$wss_port" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_wss 放行端口 ${wss_port} " >>/tmp/easytier.log
uci -q delete firewall.easytier_wss
uci set firewall.easytier_wss=rule
uci set firewall.easytier_wss.name="easytier_wss"
uci set firewall.easytier_wss.target="ACCEPT"
uci set firewall.easytier_wss.src="wan"
uci set firewall.easytier_wss.proto="tcp"
uci set firewall.easytier_wss.dest_port="$wss_port"
uci set firewall.easytier_wss.enabled="1"
fi
if [ ! -z "$ws_port" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_ws 放行端口 ${ws_port} " >>/tmp/easytier.log
uci -q delete firewall.easytier_ws
uci set firewall.easytier_ws=rule
uci set firewall.easytier_ws.name="easytier_ws"
uci set firewall.easytier_ws.target="ACCEPT"
uci set firewall.easytier_ws.src="wan"
uci set firewall.easytier_ws.proto="tcp"
uci set firewall.easytier_ws.dest_port="$ws_port"
uci set firewall.easytier_ws.enabled="1"
fi
if [ ! -z "$wg_port" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_wg 放行端口 ${wg_port} " >>/tmp/easytier.log
uci -q delete firewall.easytier_wg
uci set firewall.easytier_wg=rule
uci set firewall.easytier_wg.name="easytier_wg"
uci set firewall.easytier_wg.target="ACCEPT"
uci set firewall.easytier_wg.src="wan"
uci set firewall.easytier_wg.proto="udp"
uci set firewall.easytier_wg.dest_port="$wg_port"
uci set firewall.easytier_wg.enabled="1"
fi
if [ ! -z "$quic_port" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_quic 放行端口 ${quic_port} " >>/tmp/easytier.log
uci -q delete firewall.easytier_quic
uci set firewall.easytier_quic=rule
uci set firewall.easytier_quic.name="easytier_quic"
uci set firewall.easytier_quic.target="ACCEPT"
uci set firewall.easytier_quic.src="wan"
uci set firewall.easytier_quic.proto="tcp udp"
uci set firewall.easytier_quic.dest_port="$quic_port"
uci set firewall.easytier_quic.enabled="1"
fi
[ -z "$tunname" ] && tunname="tun0"
# Check if auto_config_interface is enabled
if [ "$auto_config_interface" = "1" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 自动网络接口配置已启用" >>/tmp/easytier.log
uci -q delete network.EasyTier >/dev/null 2>&1
if [ -z "$(uci -q get network.EasyTier)" ]; then
uci set network.EasyTier='interface'
if [ -z "$ipaddr" ]; then
uci set network.EasyTier.proto='none'
else
uci set network.EasyTier.proto='static'
uci set network.EasyTier.ipaddr=$ipaddr
[ -z "$interface_netmask" ] && interface_netmask="255.0.0.0"
uci set network.EasyTier.netmask="$interface_netmask"
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加网络接口 EasyTier 绑定虚拟接口 ${tunname},子网掩码: ${interface_netmask:-255.0.0.0}" >>/tmp/easytier.log
uci set network.EasyTier.device="$tunname"
uci set network.EasyTier.ifname="$tunname"
fi
if [ -z "$(uci -q get firewall.easytierzone)" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则,放行网络接口 EasyTier 允许出入转发,开启IP动态伪装 MSS钳制" >>/tmp/easytier.log
uci set firewall.easytierzone='zone'
uci set firewall.easytierzone.input='ACCEPT'
uci set firewall.easytierzone.output='ACCEPT'
uci set firewall.easytierzone.forward='ACCEPT'
uci set firewall.easytierzone.masq='1'
uci set firewall.easytierzone.mtu_fix='1'
uci set firewall.easytierzone.name='EasyTier'
uci set firewall.easytierzone.network='EasyTier'
fi
else
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 自动网络接口配置已禁用,不创建网络接口" >>/tmp/easytier.log
uci -q delete network.EasyTier >/dev/null 2>&1
uci -q delete firewall.easytierzone >/dev/null 2>&1
fi
et_forward="$(uci -q get easytier.@easytier[0].et_forward)"
if [ "${et_forward//etfwlan/}" != "$et_forward" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 允许从虚拟网络 EasyTier 到局域网 lan 的流量" >>/tmp/easytier.log
uci set firewall.easytierfwlan=forwarding
uci set firewall.easytierfwlan.dest='lan'
uci set firewall.easytierfwlan.src='EasyTier'
else
uci -q delete firewall.easytierfwlan
fi
if [ "${et_forward//etfwwan/}" != "$et_forward" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 允许从虚拟网络 EasyTier 到广域网 wan 的流量" >>/tmp/easytier.log
uci set firewall.easytierfwwan=forwarding
uci set firewall.easytierfwwan.dest='wan'
uci set firewall.easytierfwwan.src='EasyTier'
else
uci -q delete firewall.easytierfwwan
fi
if [ "${et_forward//lanfwet/}" != "$et_forward" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 允许从局域网 lan 到虚拟网络 EasyTier 的流量" >>/tmp/easytier.log
uci set firewall.lanfweasytier=forwarding
uci set firewall.lanfweasytier.dest='EasyTier'
uci set firewall.lanfweasytier.src='lan'
else
uci -q delete firewall.lanfweasytier
fi
if [ "${et_forward//wanfwet/}" != "$et_forward" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 允许从广域网 wan 到虚拟网络 EasyTier 的流量" >>/tmp/easytier.log
uci set firewall.wanfweasytier=forwarding
uci set firewall.wanfweasytier.dest='EasyTier'
uci set firewall.wanfweasytier.src='wan'
else
uci -q delete firewall.wanfweasytier
fi
}
start_et() {
easytierbin="$(uci -q get easytier.@easytier[0].easytierbin)"
[ -z "$easytierbin" ] && easytierbin="/usr/bin/easytier-core" && uci -q set easytier.@easytier[0].easytierbin=/usr/bin/easytier-core
etcmd="$(uci -q get easytier.@easytier[0].etcmd)"
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : easytier-core_${cpucore}开始启动" >>/tmp/easytier.log
path=$(dirname "$easytierbin")
if [ ! -f "$easytierbin" ] && [ "$size" -lt 5000 ] && [ ! -z "$size" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 自定义程序路径的可用空间不足,当前可用空间剩余${size}kb,自动切换为内存/tmp/easytier-core" >>/tmp/easytier.log
sed -i "/easytierbin/c option easytierbin '/tmp/easytier-core' " /etc/config/easytier
easytierbin=/tmp/easytier-core
fi
if [ -f "$easytierbin" ] ; then
if [ ! -x "$easytierbin" ] ; then
chmod +x "$easytierbin"
chmod +x ${path}/easytier-cli
fi
fi
if [ ! -f "$easytierbin" ] || [ "$(($($easytierbin -h 2>&1 | wc -l)))" -lt 3 ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : $easytierbin 不存在或程序不完整,开始在线下载..." >>/tmp/easytier.log
check_bin
fi
ver="$($easytierbin -V | awk {'print $2'} | tr -d '~')"
[ ! -z "$ver" ] && echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : ${easytierbin}当前版本号-${ver} " >>/tmp/easytier.log
pgrep -f easytier_check | xargs kill -9 >/dev/null 2>&1
ps | grep 'easytier-core' | grep -v grep | awk '{print $1}' | xargs kill >/dev/null 2>&1
ps | grep 'easytier-core' | grep -v grep | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
get_etconfig
if [ "$etcmd" = "config" ] ; then
#配置文件启动方式
check_config
else
procd_open_instance "easytier_core"
get_tz
[ -z "$SET_TZ" ] || procd_set_param env TZ="$SET_TZ"
core_cmd=""
if [ "$etcmd" = "etcmd" ] ; then
#默认方式启动
[ -z "$network_name" ] || core_cmd="${core_cmd} --network-name $network_name"
[ -z "$network_secret" ] || core_cmd="${core_cmd} --network-secret $network_secret"
[ -z "$ipaddr" ] || core_cmd="${core_cmd} -i $ipaddr"
[ -z "$ip6addr" ] || core_cmd="${core_cmd} --ipv6 $ip6addr"
[ "$ip_dhcp" = "0" ] || core_cmd="${core_cmd} -d"
if [ ! -z "$peeradd" ] ; then
if [[ "$(grep "list peeradd" /etc/config/easytier | awk '{print $3}' | wc -l ) " -eq 1 ]]; then
core_cmd="${core_cmd} -p $peeradd"
else
for peeraddr in $(cat /etc/config/easytier | grep 'list peeradd' | awk -F 'list peeradd' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} -p $peeraddr"
done
fi
fi
if [ ! -z "$proxy_network" ] ; then
if [[ "$(grep "list proxy_network" /etc/config/easytier | awk '{print $3}' | wc -l ) " -eq 1 ]]; then
core_cmd="${core_cmd} -n $proxy_network"
else
for proxy_networks in $(cat /etc/config/easytier | grep 'list proxy_network' | awk -F 'list proxy_network' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} -n $proxy_networks"
done
fi
fi
if [ ! -z "$exit_nodes" ] ; then
if [[ "$(grep "list exit_nodes" /etc/config/easytier | awk '{print $3}' | wc -l ) " -eq 1 ]]; then
core_cmd="${core_cmd} --exit-nodes $exit_nodes"
else
for exit_nodeadds in $(cat /etc/config/easytier | grep 'list exit_nodes' | awk -F 'list exit_nodes' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} --exit-nodes $exit_nodeadds"
done
fi
fi
if [ ! -z "$manual_routes" ] ; then
if [[ "$(grep "list manual_routes" /etc/config/easytier | awk '{print $3}' | wc -l ) " -eq 1 ]]; then
core_cmd="${core_cmd} --manual-routes $manual_routes"
else
for manual_routeadds in $(cat /etc/config/easytier | grep 'list manual_routes' | awk -F 'list manual_routes' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} --manual-routes $manual_routeadds"
done
fi
fi
if [ "$relay_network" = "1" ] ; then
if [ ! -z "$whitelist" ] ; then
if [[ "$(grep "list whitelist" /etc/config/easytier | awk '{print $3}' | wc -l ) " -eq 1 ]]; then
core_cmd="${core_cmd} --relay-network-whitelist $whitelist"
else
for whitelists in $(cat /etc/config/easytier | grep 'list whitelist' | awk -F 'list whitelist' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} --relay-network-whitelist $whitelists"
done
fi
else
core_cmd="${core_cmd} --relay-network-whitelist"
fi
fi
if [ ! -z "$mapped_listeners" ] ; then
if [[ "$(grep "list mapped_listeners" /etc/config/easytier | awk '{print $3}' | wc -l ) " -eq 1 ]]; then
core_cmd="${core_cmd} --mapped-listeners $mapped_listeners"
else
for mapped_listener in $(cat /etc/config/easytier | grep 'list mapped_listeners' | awk -F 'list mapped_listeners' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} --mapped-listeners $mapped_listener"
done
fi
fi
if [ ! -z "$port_forward" ] ; then
if [[ "$(grep "list port_forward" /etc/config/easytier | awk '{print $3}' | wc -l ) " -eq 1 ]]; then
core_cmd="${core_cmd} --port-forward $port_forward"
else
for port_forwards in $(cat /etc/config/easytier | grep 'list port_forward' | awk -F 'list port_forward' '{print $2}' | sed "s/'/\n/g" | tr -d " ' "); do
core_cmd="${core_cmd} --port-forward $port_forwards"
done
fi
fi
[ -z "$rpc_portal" ] || core_cmd="${core_cmd} -r $rpc_portal"
[ -z "$rpc_portal_whitelist" ] || core_cmd="${core_cmd} --rpc-portal-whitelist $rpc_portal_whitelist"
[ -z "$tcp_port" ] || core_cmd="${core_cmd} -l tcp:$tcp_port"
[ -z "$tcp_port" ] || core_cmd="${core_cmd} -l udp:$tcp_port"
[ -z "$ws_port" ] || core_cmd="${core_cmd} -l ws:$ws_port"
[ -z "$wss_port" ] || core_cmd="${core_cmd} -l wss:$wss_port"
[ -z "$wg_port" ] || core_cmd="${core_cmd} -l wg:$wg_port"
[ -z "$quic_port" ] || core_cmd="${core_cmd} -l quic:$quic_port"
[ -z "$external_node" ] || core_cmd="${core_cmd} -e $external_node"
[ "$listenermode" = "ON" ] || core_cmd="${core_cmd} --no-listener"
[ -z "$desvice_name" ] || core_cmd="${core_cmd} --hostname $desvice_name"
[ -z "$instance_name" ] || core_cmd="${core_cmd} -m $instance_name"
[ -z "$vpn_portal" ] || core_cmd="${core_cmd} --vpn-portal $vpn_portal"
[ -z "$mtu" ] || core_cmd="${core_cmd} --mtu $mtu"
[ "$default_protocol" = "-" ] || core_cmd="${core_cmd} --default-protocol $default_protocol"
[ -z "$tunname" ] || core_cmd="${core_cmd} --dev-name $tunname"
[ "$disable_encryption" = "0" ] || core_cmd="${core_cmd} -u"
[ "$multi_thread" = "0" ] || core_cmd="${core_cmd} --multi-thread"
[ -z "$multi_thread_count" ] || core_cmd="${core_cmd} --multi-thread-count $multi_thread_count"
[ "$no_tun" = "0" ] || core_cmd="${core_cmd} --no-tun"
[ "$smoltcp" = "0" ] || core_cmd="${core_cmd} --use-smoltcp"
[ "$disable_ipv6" = "0" ] || core_cmd="${core_cmd} --disable-ipv6"
[ "$latency_first" = "0" ] || core_cmd="${core_cmd} --latency-first"
[ "$exit_node" = "0" ] || core_cmd="${core_cmd} --enable-exit-node"
[ "$disable_p2p" = "0" ] || core_cmd="${core_cmd} --disable-p2p"
[ "$relay_all" = "0" ] || core_cmd="${core_cmd} --relay-all-peer-rpc"
[ -z "$socks_port" ] || core_cmd="${core_cmd} --socks5 $socks_port"
[ "$comp" = "none" ] || core_cmd="${core_cmd} --compression $comp"
[ "$bind_device" = "0" ] || core_cmd="${core_cmd} --bind-device true"
[ "$bind_device" = "1" ] || core_cmd="${core_cmd} --bind-device false"
[ "$kcp_proxy" = "0" ] || core_cmd="${core_cmd} --enable-kcp-proxy"
[ "$kcp_input" = "0" ] || core_cmd="${core_cmd} --disable-kcp-input"
[ "$proxy_forward" = "0" ] || core_cmd="${core_cmd} --proxy-forward-by-system"
[ "$accept_dns" = "0" ] || core_cmd="${core_cmd} --accept-dns true"
[ "$private_mode" = "0" ] || core_cmd="${core_cmd} --private-mode true"
[ "$quic_proxy" = "0" ] || core_cmd="${core_cmd} --enable-quic-proxy"
[ "$quic_input" = "0" ] || core_cmd="${core_cmd} --disable-quic-input"
[ -z "$foreign_relay_bps_limit" ] || core_cmd="${core_cmd} --foreign-relay-bps-limit $foreign_relay_bps_limit"
if [ -n "$encryption_algorithm" ] && [ "$encryption_algorithm" != "aes-gcm" ]; then
core_cmd="${core_cmd} --encryption-algorithm ${encryption_algorithm}"
fi
[ "$p2p_only" = "0" ] || core_cmd="${core_cmd} --p2p-only"
[ "$disable_tcp" = "0" ] || core_cmd="${core_cmd} --disable-tcp-hole-punching"
[ "$disable_udp" = "0" ] || core_cmd="${core_cmd} --disable-udp-hole-punching"
[ "$disable_sym" = "0" ] || core_cmd="${core_cmd} --disable-sym-hole-punching"
[ "$disable_relay_kcp" = "0" ] || core_cmd="${core_cmd} --disable-relay-kcp true"
[ "$relay_kcp" = "0" ] || core_cmd="${core_cmd} --enable-relay-foreign-network-kcp true"
[ -z "$udp_white_port" ] || core_cmd="${core_cmd} --udp-whitelist $udp_white_port"
[ -z "$tcp_white_port" ] || core_cmd="${core_cmd} --tcp-whitelist $tcp_white_port"
[ -z "$extra_args" ] || core_cmd="${core_cmd} $extra_args"
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 运行 ${easytierbin} ${core_cmd} --console-log-level ${log}" >>/tmp/easytier.log
fi
if [ "$etcmd" = "web" ] ; then
#WEB控制台启动
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 运行 ${easytierbin} -w ${web_config}" >>/tmp/easytier.log
[ -f /etc/easytier/et_machine_id ] || touch /etc/easytier/et_machine_id
if [ ! -s /etc/easytier/et_machine_id ]; then
cat /proc/sys/kernel/random/uuid > /etc/easytier/et_machine_id
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 生成新的 UUID 到 /etc/easytier/et_machine_id" >>/tmp/easytier.log
fi
et_uuid="$(cat /etc/easytier/et_machine_id | tr -d ' \n')"
[ -z "$et_uuid" ] || core_cmd="${core_cmd} --machine-id $et_uuid"
[ -z "$web_config" ] || core_cmd="${core_cmd} -w $web_config"
[ -z "$desvice_name" ] || core_cmd="${core_cmd} --hostname $desvice_name"
fi
procd_set_param command /bin/sh -c "
cd /tmp ; ${easytierbin} ${core_cmd} --console-log-level ${log} >> /tmp/easytier.log 2>&1 &
ET_CORE_PID=\$!
{
while true; do
LOG_SIZE=\$(ls -l /tmp/easytier.log | awk '{print int(\$5/1024)}')
if [ \$LOG_SIZE -gt 5120 ]; then
tail -n 500 /tmp/easytier.log > /tmp/easytier.log.tmp
mv /tmp/easytier.log.tmp /tmp/easytier.log
fi
sleep 300
done
} &
LOG_MANAGEMENT_PID=\$!
trap \"kill -TERM \$ET_CORE_PID; kill -TERM \$LOG_MANAGEMENT_PID; exit\" SIGINT SIGTERM EXIT
wait \$ET_CORE_PID
"
fi
procd_set_param limits core="unlimited"
procd_set_param limits nofile="1000000 1000000"
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
if [ ! -z "$socks_port" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_socks5 放行端口 ${socks_port} " >>/tmp/easytier.log
uci -q delete firewall.easytier_socks5
uci set firewall.easytier_socks5=rule
uci set firewall.easytier_socks5.name="easytier_socks5"
uci set firewall.easytier_socks5.target="ACCEPT"
uci set firewall.easytier_socks5.src="wan"
uci set firewall.easytier_socks5.proto="tcp"
uci set firewall.easytier_socks5.dest_port="$socks_port"
uci set firewall.easytier_socks5.enabled="1"
fi
if [ "$listenermode" = "ON" ] || [ "$etcmd" = "web" ] ; then
if [ -z "$tcp_port" ] && [ -z "$ws_port" ] && [ -z "$wss_port" ] && [ -z "$wg_port" ] ; then
tcp_port="11010"
ws_port="11011"
wss_port="11012"
wg_port="11011"
fi
fi
set_firewall
sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1
[ ! -z "$checkip" ] && check
echo `date +%s` > /tmp/easytier_time
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 启动完成!" >>/tmp/easytier.log
}
start_web() {
webbin="$(uci -q get easytier.@easytier[0].webbin)"
path=$(dirname "$webbin")
if [ -f /tmp/easytier-web-embed ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 找到上传的easytier-web程序,替换${webbin} " >>/tmp/easytierweb.log
mv -f /tmp/easytier-web-embed "$webbin"
fi
if [ ! -f "$webbin" ] || [ "$(($($webbin -h 2>&1 | wc -l)))" -lt 3 ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : $webbin 不存在或程序不完整,开始在线下载..." >>/tmp/easytierweb.log
check_bin
fi
if [ -f "$webbin" ] ; then
[ ! -x "$webbin" ] && chmod +x "$webbin"
fi
if [ ! -f "$webbin" ] || [ "$($webbin -h 2>&1 | wc -l)" -lt 3 ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 未找到${webbin}程序或程序不适配当前架构,请上传安装后再启动! " >>/tmp/easytierweb.log
return 1
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 开始启动${webbin} " >>/tmp/easytierweb.log
db_path="$(uci -q get easytier.@easytier[0].web_db_path)"
web_protocol="$(uci -q get easytier.@easytier[0].web_protocol)"
web_port="$(uci -q get easytier.@easytier[0].web_port)"
api_port="$(uci -q get easytier.@easytier[0].web_api_port)"
html_port="$(uci -q get easytier.@easytier[0].web_html_port)"
weblog="$(uci -q get easytier.@easytier[0].web_weblog)"
weblog="${weblog:-off}"
api_host="$(uci -q get easytier.@easytier[0].web_api_host)"
geoip_db="$(uci -q get easytier.@easytier[0].web_geoip_db)"
fw_web="$(uci -q get easytier.@easytier[0].web_fw_web || echo 0)"
fw_api="$(uci -q get easytier.@easytier[0].web_fw_api || echo 0)"
api_addr="$(uci -q get easytier.@easytier[0].web_api_addr)"
html_addr="$(uci -q get easytier.@easytier[0].web_html_addr)"
disable_registration="$(uci -q get easytier.@easytier[0].web_disable_registration || echo 0)"
allow_auto_create_user="$(uci -q get easytier.@easytier[0].web_allow_auto_create_user || echo 0)"
# OIDC配置
oidc_enabled="$(uci -q get easytier.@easytier[0].web_oidc_enabled || echo 0)"
oidc_issuer_url="$(uci -q get easytier.@easytier[0].web_oidc_issuer_url)"
oidc_client_id="$(uci -q get easytier.@easytier[0].web_oidc_client_id)"
oidc_client_secret="$(uci -q get easytier.@easytier[0].web_oidc_client_secret)"
oidc_redirect_url="$(uci -q get easytier.@easytier[0].web_oidc_redirect_url)"
oidc_username_claim="$(uci -q get easytier.@easytier[0].web_oidc_username_claim)"
oidc_scopes="$(uci -q get easytier.@easytier[0].web_oidc_scopes)"
oidc_frontend_base_url="$(uci -q get easytier.@easytier[0].web_oidc_frontend_base_url)"
oidc_disable_pkce="$(uci -q get easytier.@easytier[0].web_oidc_disable_pkce || echo 0)"
# Webhook配置
webhook_enabled="$(uci -q get easytier.@easytier[0].web_webhook_enabled || echo 0)"
webhook_url="$(uci -q get easytier.@easytier[0].web_webhook_url)"
webhook_secret="$(uci -q get easytier.@easytier[0].web_webhook_secret)"
internal_auth_token="$(uci -q get easytier.@easytier[0].web_internal_auth_token)"
web_instance_id="$(uci -q get easytier.@easytier[0].web_web_instance_id)"
web_instance_api_base_url="$(uci -q get easytier.@easytier[0].web_web_instance_api_base_url)"
procd_open_instance "easytier_web"
get_tz
[ -z "$SET_TZ" ] || procd_set_param env TZ="$SET_TZ"
web_cmd=""
[ -z "$db_path" ] || web_cmd="${web_cmd} -d $db_path"
[ -z "$web_protocol" ] || web_cmd="${web_cmd} -p $web_protocol"
[ -z "$web_port" ] || web_cmd="${web_cmd} -c $web_port"
[ -z "$api_port" ] || web_cmd="${web_cmd} -a $api_port"
[ -z "$api_host" ] || web_cmd="${web_cmd} --api-host $api_host"
[ -z "$geoip_db" ] || web_cmd="${web_cmd} --geoip-db $geoip_db"
if [ -z "$html_port" ] ; then
web_cmd="${web_cmd} --no-web"
else
web_cmd="${web_cmd} -l $html_port"
fi
# 基础配置参数
[ -n "$api_addr" ] && [ "$api_addr" != "0.0.0.0" ] && web_cmd="${web_cmd} --api-server-addr $api_addr"
[ -n "$html_addr" ] && [ "$html_addr" != "0.0.0.0" ] && web_cmd="${web_cmd} --web-server-addr $html_addr"
[ "$disable_registration" = "1" ] && web_cmd="${web_cmd} --disable-registration"
[ "$allow_auto_create_user" = "1" ] && web_cmd="${web_cmd} --allow-auto-create-user"
# OIDC配置参数
if [ "$oidc_enabled" = "1" ] ; then
[ -z "$oidc_issuer_url" ] || web_cmd="${web_cmd} --oidc-issuer-url $oidc_issuer_url"
[ -z "$oidc_client_id" ] || web_cmd="${web_cmd} --oidc-client-id $oidc_client_id"
[ -z "$oidc_client_secret" ] || web_cmd="${web_cmd} --oidc-client-secret $oidc_client_secret"
[ -z "$oidc_redirect_url" ] || web_cmd="${web_cmd} --oidc-redirect-url $oidc_redirect_url"
[ -z "$oidc_username_claim" ] || web_cmd="${web_cmd} --oidc-username-claim $oidc_username_claim"
[ -z "$oidc_scopes" ] || web_cmd="${web_cmd} --oidc-scopes \"$oidc_scopes\""
[ -z "$oidc_frontend_base_url" ] || web_cmd="${web_cmd} --oidc-frontend-base-url $oidc_frontend_base_url"
[ "$oidc_disable_pkce" = "1" ] && web_cmd="${web_cmd} --oidc-disable-pkce"
fi
# Webhook配置参数
if [ "$webhook_enabled" = "1" ] ; then
[ -z "$webhook_url" ] || web_cmd="${web_cmd} --webhook-url $webhook_url"
[ -z "$webhook_secret" ] || web_cmd="${web_cmd} --webhook-secret $webhook_secret"
[ -z "$internal_auth_token" ] || web_cmd="${web_cmd} --internal-auth-token $internal_auth_token"
[ -z "$web_instance_id" ] || web_cmd="${web_cmd} --web-instance-id $web_instance_id"
[ -z "$web_instance_api_base_url" ] || web_cmd="${web_cmd} --web-instance-api-base-url $web_instance_api_base_url"
fi
if [ "$weblog" != "off" ] ; then
web_cmd="${web_cmd} --console-log-level $weblog"
fi
procd_set_param command /bin/sh -c "
cd /tmp ; ${webbin} ${web_cmd} >> /tmp/easytierweb.log 2>&1 &
ET_WEB_PID=\$!
{
while true; do
LOG_SIZE=\$(ls -l /tmp/easytierweb.log | awk '{print int(\$5/1024)}')
if [ \$LOG_SIZE -gt 5120 ]; then
tail -n 500 /tmp/easytierweb.log > /tmp/easytierweb.log.tmp
mv /tmp/easytierweb.log.tmp /tmp/easytierweb.log
fi
sleep 300
done
} &
LOG_MANAGEMENT_PID=\$!
trap \"kill -TERM \$ET_WEB_PID; kill -TERM \$LOG_MANAGEMENT_PID; exit\" SIGINT SIGTERM EXIT
wait \$ET_WEB_PID
"
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param respawn
procd_close_instance
if [ ! -z "$web_port" ] && [ "$fw_web" = "1" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_web 放行服务端口 ${web_port} " >>/tmp/easytierweb.log
uci -q delete firewall.easytier_webserver
uci set firewall.easytier_webserver=rule
uci set firewall.easytier_webserver.name="easytier_webserver"
uci set firewall.easytier_webserver.target="ACCEPT"
uci set firewall.easytier_webserver.src="wan"
uci set firewall.easytier_webserver.proto="tcp udp"
uci set firewall.easytier_webserver.dest_port="$web_port"
uci set firewall.easytier_webserver.enabled="1"
fi
if [ ! -z "$api_port" ] && [ "$fw_api" = "1" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_web 放行API端口 ${api_port} " >>/tmp/easytierweb.log
uci -q delete firewall.easytier_webapi
uci set firewall.easytier_webapi=rule
uci set firewall.easytier_webapi.name="easytier_webapi"
uci set firewall.easytier_webapi.target="ACCEPT"
uci set firewall.easytier_webapi.src="wan"
uci set firewall.easytier_webapi.proto="tcp"
uci set firewall.easytier_webapi.dest_port="$api_port"
uci set firewall.easytier_webapi.enabled="1"
fi
if [ ! -z "$html_port" ] && [ "$fw_api" = "1" ] && [ "$html_port" != "$api_port" ] ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 添加防火墙规则 easytier_web 放行html端口 ${html_port} " >>/tmp/easytierweb.log
uci -q delete firewall.easytier_webhtml
uci set firewall.easytier_webhtml=rule
uci set firewall.easytier_webhtml.name="easytier_webhtml"
uci set firewall.easytier_webhtml.target="ACCEPT"
uci set firewall.easytier_webhtml.src="wan"
uci set firewall.easytier_webhtml.proto="tcp"
uci set firewall.easytier_webhtml.dest_port="$html_port"
uci set firewall.easytier_webhtml.enabled="1"
fi
echo `date +%s` > /tmp/easytierweb_time
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 启动完成! " >>/tmp/easytierweb.log
}
start_service() {
echo "start.."
cputype=$(uname -ms | tr ' ' '_' | tr '[A-Z]' '[a-z]')
[ -n "$(echo $cputype | grep -E "linux.*armv.*")" ] && cpucore="arm"
[ -n "$(echo $cputype | grep -E "linux.*armv7.*")" ] && [ -n "$(cat /proc/cpuinfo | grep vfp)" ] && cpucore="armv7"
[ -n "$(echo $cputype | grep -E "linux.*aarch64.*|linux.*armv8.*")" ] && cpucore="aarch64"
[ -n "$(echo $cputype | grep -E "linux.*86.*")" ] && cpucore="i386"
[ -n "$(echo $cputype | grep -E "linux.*86_64.*")" ] && cpucore="x86_64"
if [ -n "$(echo $cputype | grep -E "linux.*mips.*")" ] ; then
mipstype=$(echo -n I | hexdump -o 2>/dev/null | awk '{ print substr($2,6,1); exit}')
[ "$mipstype" = "0" ] && cpucore="mips" || cpucore="mipsel"
fi
echo "" >/tmp/easytier.log
echo "" >/tmp/easytierweb.log
size="$(df -k | awk '/\/overlay$/ {sub(/K$/, "", $4); print $4}')"
[ -z "$size" ] && size="$(df -kP /usr/bin | awk 'NR==2 {print $(NF-2) }')"
if [ -z "$(uci -q get easytier.@easytier[0].web_enabled)" ] ; then
uci -q set easytier.@easytier[0].web_enabled='0'
uci commit easytier
fi
[ "$(uci -q get easytier.@easytier[0].web_enabled)" = "1" ] && start_web
[ "$(uci -q get easytier.@easytier[0].enabled)" = "1" ] && start_et easytier
# 延迟重载网络配置,避免与拨号等网络事件冲突
if [ -n "$(uci changes network)" ] || [ -n "$(uci changes firewall)" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 检测到配置变更,将在5秒后后台重载... " >>/tmp/easytier.log
(
sleep 5
# 使用锁文件防止重复重载
LOCK_FILE="/var/lock/easytier_reload.lock"
if [ ! -f "$LOCK_FILE" ]; then
touch "$LOCK_FILE"
[ -n "$(uci changes network)" ] && uci commit network && /etc/init.d/network reload >/dev/null 2>&1
[ -n "$(uci changes firewall)" ] && uci commit firewall && /etc/init.d/firewall reload >/dev/null 2>&1
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 网络配置重载完成 " >>/tmp/easytier.log
rm -f "$LOCK_FILE"
fi
) &
fi
}
stop_service() {
echo "stop.."
if [ "$(uci -q get easytier.@easytier[0].enabled)" = "0" ] ; then
pgrep -f easytier_check | xargs kill -9 >/dev/null 2>&1
ps | grep 'easytier-core' | grep -v grep | awk '{print $1}' | xargs kill >/dev/null 2>&1
ps | grep 'easytier-core' | grep -v grep | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
uci -q delete network.EasyTier >/dev/null 2>&1
uci -q delete firewall.easytierzone >/dev/null 2>&1
uci -q delete firewall.easytierfwlan >/dev/null 2>&1
uci -q delete firewall.easytierfwwan >/dev/null 2>&1
uci -q delete firewall.lanfweasytier >/dev/null 2>&1
uci -q delete firewall.wanfweasytier >/dev/null 2>&1
uci -q delete firewall.easytier_tcp >/dev/null 2>&1
uci -q delete firewall.easytier_udp >/dev/null 2>&1
uci -q delete firewall.easytier_tcp_udp >/dev/null 2>&1
uci -q delete firewall.easytier_wss >/dev/null 2>&1
uci -q delete firewall.easytier_ws >/dev/null 2>&1
uci -q delete firewall.easytier_wg >/dev/null 2>&1
uci -q delete firewall.easytier_quic >/dev/null 2>&1
uci -q delete firewall.easytier_wireguard >/dev/null 2>&1
uci -q delete firewall.easytier_socks5 >/dev/null 2>&1
# 延迟重载,避免与网络事件冲突
if [ -n "$(uci changes network)" ] || [ -n "$(uci changes firewall)" ]; then
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : 停止服务,将在5秒后后台重载配置... " >>/tmp/easytier.log
(
sleep 5
LOCK_FILE="/var/lock/easytier_reload.lock"
if [ ! -f "$LOCK_FILE" ]; then
touch "$LOCK_FILE"
[ -n "$(uci changes network)" ] && uci commit network && /etc/init.d/network reload >/dev/null 2>&1
[ -n "$(uci changes firewall)" ] && uci commit firewall && /etc/init.d/firewall reload >/dev/null 2>&1
rm -f "$LOCK_FILE"
fi
) &
fi
rm -rf /tmp/easytier-cli_peer /tmp/easytier-cli_connector /tmp/easytier-cli_stun /tmp/easytier-cli_route /tmp/easytier-cli_acl >/dev/null 2>&1
rm -rf /tmp/easytier-cli_peer-center /tmp/easytier-cli_vpn-portal /tmp/easytier_cmd /tmp/easytier-cli_node /tmp/easytier-cli_mapped_listener >/dev/null 2>&1
rm -rf /tmp/easytier-*.* /tmp/easytier.log >/dev/null 2>&1
echo "$(date '+%Y-%m-%d %H:%M:%S') easytier : easytier-core 停止运行" >>/tmp/easytier.log
fi
if [ "$(uci -q get easytier.@easytier[0].web_enabled)" = "0" ] ; then
ps | grep 'easytier-web' | grep -v grep | awk '{print $1}' | xargs kill >/dev/null 2>&1
ps | grep 'easytier-web' | grep -v grep | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
uci -q delete firewall.easytier_webserver
uci -q delete firewall.easytier_webapi
uci -q delete firewall.easytier_webhtml
fi
}
reload_service() {
stop
start
}
service_triggers() {
procd_add_reload_trigger "easytier"
#如果WAN网络接口上线则触发重新启动easytier
# procd_add_interface_trigger "interface.*.up" wan /etc/init.d/easytier reload
}
@@ -0,0 +1,13 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
delete ucitrack.@easytier[-1]
add ucitrack easytier
set ucitrack.@easytier[-1].init=easytier
commit ucitrack
EOF
chmod +x /etc/init.d/easytier
/etc/init.d/easytier enable
rm -rf /tmp/luci-indexcache /tmp/luci-modulecache
exit 0
@@ -0,0 +1,151 @@
#!/bin/sh
# EasyTier Download Management
# Handles binary downloads from GitHub with proxy support
# 引入工具函数
. /usr/share/easytier/utils.sh 2>/dev/null || true
# 默认 GitHub 加速代理列表
DEFAULT_PROXYS="
https://ghproxy.net/
https://gh-proxy.com/
https://cdn.gh-proxy.com/
https://ghfast.top/
"
# 获取代理列表
# 优先从 UCI 配置读取,否则使用默认值
get_proxy_list() {
local proxys=$(uci -q get easytier.@easytier[0].github_proxys)
[ -z "$proxys" ] && proxys="$DEFAULT_PROXYS"
echo "$proxys"
}
# 获取最新版本号
get_latest_version() {
local user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
local tag=""
local curltest=$(which curl)
if [ -z "$curltest" ] || [ ! -s "$(which curl)" ]; then
tag=$(wget --no-check-certificate -T 5 -t 3 --user-agent "$user_agent" --max-redirect=0 --output-document=- \
https://api.github.com/repos/EasyTier/EasyTier/releases/latest 2>&1 | grep 'tag_name' | cut -d\" -f4)
[ -z "$tag" ] && tag=$(wget --no-check-certificate -T 5 -t 3 --user-agent "$user_agent" --quiet --output-document=- \
https://api.github.com/repos/EasyTier/EasyTier/releases/latest 2>&1 | grep 'tag_name' | cut -d\" -f4)
else
tag=$(curl -k --connect-timeout 3 --user-agent "$user_agent" \
https://api.github.com/repos/EasyTier/EasyTier/releases/latest 2>&1 | grep 'tag_name' | cut -d\" -f4)
[ -z "$tag" ] && tag=$(curl -Lk --connect-timeout 3 --user-agent "$user_agent" -s \
https://api.github.com/repos/EasyTier/EasyTier/releases/latest 2>&1 | grep 'tag_name' | cut -d\" -f4)
fi
# 如果获取失败,从 UCI 配置或使用默认版本
if [ -z "$tag" ]; then
tag=$(uci -q get easytier.@easytier[0].fallback_version)
[ -z "$tag" ] && tag="v2.6.2"
fi
echo "$tag"
}
# 下载二进制文件
# 参数: $1=版本号 $2=CPU架构 $3=目标路径
download_binary() {
local tag="$1"
local cpucore="$2"
local path="$3"
local proxys=$(get_proxy_list)
local download_url="https://github.com/EasyTier/EasyTier/releases/download/${tag}/easytier-linux-${cpucore}-${tag}.zip"
mkdir -p "$path"
for proxy in $proxys; do
log_message "INFO" "easytier" "尝试使用代理 ${proxy} 下载" "/tmp/easytier.log"
if curl -L -k -o /tmp/easytier.zip --connect-timeout 10 --retry 3 "${proxy}${download_url}" || \
wget --no-check-certificate --timeout=10 --tries=3 -O /tmp/easytier.zip "${proxy}${download_url}"; then
unzip -j -q -o /tmp/easytier.zip -d /tmp
chmod +x /tmp/easytier-core /tmp/easytier-cli /tmp/easytier-web /tmp/easytier-web-embed 2>/dev/null || true
rm -rf /tmp/easytier.zip
log_message "INFO" "easytier" "下载成功" "/tmp/easytier.log"
return 0
else
log_message "WARN" "easytier" "${proxy}${download_url} 下载失败" "/tmp/easytier.log"
fi
done
log_message "ERROR" "easytier" "所有代理下载均失败,请手动下载上传程序" "/tmp/easytier.log"
return 1
}
# 检查并下载程序
# 参数: $1=程序路径 $2=目标路径 $3=CPU架构
check_and_download() {
local easytierbin="$1"
local path="$2"
local cpucore="$3"
# 检查程序是否存在且完整
if [ ! -f "$easytierbin" ] || [ "$($easytierbin -h 2>&1 | wc -l)" -lt 3 ]; then
log_message "INFO" "easytier" "$easytierbin 不存在或程序不完整,开始在线下载..." "/tmp/easytier.log"
local tag=$(get_latest_version)
log_message "INFO" "easytier" "开始在线下载${tag}版本" "/tmp/easytier.log"
if download_binary "$tag" "$cpucore" "$path"; then
# 移动下载的文件到目标位置
if [ "$(uci -q get easytier.@easytier[0].enabled)" = "1" ]; then
mv -f /tmp/easytier-core "${path}/" 2>/dev/null
mv -f /tmp/easytier-cli "${path}/" 2>/dev/null
chmod +x "$easytierbin" 2>/dev/null
fi
if [ "$(uci -q get easytier.@easytier[0].web_enabled)" = "1" ]; then
local webbin=$(uci -q get easytier.@easytier[0].webbin)
[ -z "$webbin" ] && webbin="/usr/bin/easytier-web"
mv -f /tmp/easytier-web-embed "$webbin" 2>/dev/null || true
chmod +x "$webbin" 2>/dev/null
fi
return 0
fi
return 1
fi
return 0
}
# 处理上传的程序
# 参数: $1=目标路径 $2=程序路径
handle_uploaded_binary() {
local path="$1"
local easytierbin="$2"
local size=$(get_available_space "$path")
if [ -f /tmp/easytier-core ] || [ -f /tmp/easytier-cli ]; then
if [ "${path:0:4}" != "/tmp" ]; then
chmod +x /tmp/easytier-core 2>/dev/null
chmod +x /tmp/easytier-cli 2>/dev/null
mkdir -p "$path"
log_message "INFO" "easytier" "找到上传的程序/tmp/easytier-core,替换为$easytierbin" "/tmp/easytier.log"
local upsize=$(du -k /tmp/easytier-core 2>/dev/null | cut -f1)
local result=$((size - upsize))
if [ "$(/tmp/easytier-core -h 2>&1 | wc -l)" -gt 3 ] && [ "$result" -gt 1000 ]; then
mv -f /tmp/easytier-core "$easytierbin" 2>/dev/null
mv -f /tmp/easytier-cli "${path}/easytier-cli" 2>/dev/null
return 0
else
log_message "WARN" "easytier" "无法替换,上传的程序不完整或自定义路径的可用空间不足,当前空间剩余${size}kb" "/tmp/easytier.log"
return 1
fi
fi
fi
return 1
}
@@ -0,0 +1,204 @@
#!/bin/sh
# EasyTier Firewall Management
# Manages firewall rules and network interfaces for EasyTier
# 引入工具函数
. /usr/share/easytier/utils.sh 2>/dev/null || true
# 添加单条防火墙规则
# 参数: $1=规则名称 $2=协议 $3=端口 $4=描述
add_firewall_rule() {
local rule_name="$1"
local proto="$2"
local port="$3"
local desc="$4"
[ -z "$port" ] && return 1
log_message "INFO" "easytier" "添加防火墙规则 ${rule_name} 放行端口 ${port}" "/tmp/easytier.log"
uci -q delete "firewall.${rule_name}"
uci set "firewall.${rule_name}=rule"
uci set "firewall.${rule_name}.name=${rule_name}"
uci set "firewall.${rule_name}.target=ACCEPT"
uci set "firewall.${rule_name}.src=wan"
uci set "firewall.${rule_name}.proto=${proto}"
uci set "firewall.${rule_name}.dest_port=${port}"
uci set "firewall.${rule_name}.enabled=1"
}
# 设置所有 EasyTier 防火墙规则
# 需要设置的变量: tcp_port, udp_port, ws_port, wss_port, wg_port, quic_port, socks_port
set_firewall_rules() {
[ -n "$tcp_port" ] && add_firewall_rule "easytier_tcp_udp" "tcp udp" "$tcp_port" "EasyTier TCP/UDP"
[ -n "$udp_port" ] && add_firewall_rule "easytier_udp" "udp" "$udp_port" "EasyTier UDP"
[ -n "$ws_port" ] && add_firewall_rule "easytier_ws" "tcp" "$ws_port" "EasyTier WS"
[ -n "$wss_port" ] && add_firewall_rule "easytier_wss" "tcp" "$wss_port" "EasyTier WSS"
[ -n "$wg_port" ] && add_firewall_rule "easytier_wg" "udp" "$wg_port" "EasyTier WG"
[ -n "$quic_port" ] && add_firewall_rule "easytier_quic" "tcp udp" "$quic_port" "EasyTier QUIC"
[ -n "$socks_port" ] && add_firewall_rule "easytier_socks5" "tcp" "$socks_port" "EasyTier SOCKS5"
}
# 设置网络接口
# 参数: $1=接口名称 $2=IP地址(可选)
setup_network_interface() {
local tunname="${1:-tun0}"
local ipaddr="$2"
uci -q delete network.EasyTier >/dev/null 2>&1
if [ -z "$(uci -q get network.EasyTier)" ]; then
uci set network.EasyTier='interface'
if [ -z "$ipaddr" ]; then
uci set network.EasyTier.proto='none'
else
uci set network.EasyTier.proto='static'
uci set network.EasyTier.ipaddr="$ipaddr"
uci set network.EasyTier.netmask='255.0.0.0'
fi
log_message "INFO" "easytier" "添加网络接口 EasyTier 绑定虚拟接口 ${tunname}" "/tmp/easytier.log"
uci set network.EasyTier.device="$tunname"
uci set network.EasyTier.ifname="$tunname"
fi
}
# 设置防火墙区域
setup_firewall_zone() {
if [ -z "$(uci -q get firewall.easytierzone)" ]; then
log_message "INFO" "easytier" "添加防火墙规则,放行网络接口 EasyTier 允许出入转发,开启IP动态伪装 MSS钳制" "/tmp/easytier.log"
uci set firewall.easytierzone='zone'
uci set firewall.easytierzone.input='ACCEPT'
uci set firewall.easytierzone.output='ACCEPT'
uci set firewall.easytierzone.forward='ACCEPT'
uci set firewall.easytierzone.masq='1'
uci set firewall.easytierzone.mtu_fix='1'
uci set firewall.easytierzone.name='EasyTier'
uci set firewall.easytierzone.network='EasyTier'
fi
}
# 设置转发规则
# 参数: $1=et_forward 配置值
setup_forwarding_rules() {
local et_forward="$1"
if [ "${et_forward#*etfwlan}" != "$et_forward" ]; then
log_message "INFO" "easytier" "允许从虚拟网络 EasyTier 到局域网 lan 的流量" "/tmp/easytier.log"
uci set firewall.easytierfwlan=forwarding
uci set firewall.easytierfwlan.dest='lan'
uci set firewall.easytierfwlan.src='EasyTier'
else
uci -q delete firewall.easytierfwlan
fi
if [ "${et_forward#*etfwwan}" != "$et_forward" ]; then
log_message "INFO" "easytier" "允许从虚拟网络 EasyTier 到广域网 wan 的流量" "/tmp/easytier.log"
uci set firewall.easytierfwwan=forwarding
uci set firewall.easytierfwwan.dest='wan'
uci set firewall.easytierfwwan.src='EasyTier'
else
uci -q delete firewall.easytierfwwan
fi
if [ "${et_forward#*lanfwet}" != "$et_forward" ]; then
log_message "INFO" "easytier" "允许从局域网 lan 到虚拟网络 EasyTier 的流量" "/tmp/easytier.log"
uci set firewall.lanfweasytier=forwarding
uci set firewall.lanfweasytier.dest='EasyTier'
uci set firewall.lanfweasytier.src='lan'
else
uci -q delete firewall.lanfweasytier
fi
if [ "${et_forward#*wanfwet}" != "$et_forward" ]; then
log_message "INFO" "easytier" "允许从广域网 wan 到虚拟网络 EasyTier 的流量" "/tmp/easytier.log"
uci set firewall.wanfweasytier=forwarding
uci set firewall.wanfweasytier.dest='EasyTier'
uci set firewall.wanfweasytier.src='wan'
else
uci -q delete firewall.wanfweasytier
fi
}
# 清理所有 EasyTier 防火墙规则
clean_firewall_rules() {
uci -q delete network.EasyTier >/dev/null 2>&1
uci -q delete firewall.easytierzone >/dev/null 2>&1
uci -q delete firewall.easytierfwlan >/dev/null 2>&1
uci -q delete firewall.easytierfwwan >/dev/null 2>&1
uci -q delete firewall.lanfweasytier >/dev/null 2>&1
uci -q delete firewall.wanfweasytier >/dev/null 2>&1
uci -q delete firewall.easytier_tcp >/dev/null 2>&1
uci -q delete firewall.easytier_udp >/dev/null 2>&1
uci -q delete firewall.easytier_tcp_udp >/dev/null 2>&1
uci -q delete firewall.easytier_wss >/dev/null 2>&1
uci -q delete firewall.easytier_ws >/dev/null 2>&1
uci -q delete firewall.easytier_wg >/dev/null 2>&1
uci -q delete firewall.easytier_quic >/dev/null 2>&1
uci -q delete firewall.easytier_wireguard >/dev/null 2>&1
uci -q delete firewall.easytier_socks5 >/dev/null 2>&1
uci -q delete firewall.easytier_webserver >/dev/null 2>&1
uci -q delete firewall.easytier_webapi >/dev/null 2>&1
uci -q delete firewall.easytier_webhtml >/dev/null 2>&1
}
# 设置 Web 控制台防火墙规则
# 参数: $1=web_port $2=api_port $3=html_port $4=fw_web $5=fw_api
setup_web_firewall() {
local web_port="$1"
local api_port="$2"
local html_port="$3"
local fw_web="$4"
local fw_api="$5"
if [ -n "$web_port" ] && [ "$fw_web" = "1" ]; then
log_message "INFO" "easytier" "添加防火墙规则 easytier_web 放行服务端口 ${web_port}" "/tmp/easytierweb.log"
uci -q delete firewall.easytier_webserver
uci set firewall.easytier_webserver=rule
uci set firewall.easytier_webserver.name="easytier_webserver"
uci set firewall.easytier_webserver.target="ACCEPT"
uci set firewall.easytier_webserver.src="wan"
uci set firewall.easytier_webserver.proto="tcp udp"
uci set firewall.easytier_webserver.dest_port="$web_port"
uci set firewall.easytier_webserver.enabled="1"
fi
if [ -n "$api_port" ] && [ "$fw_api" = "1" ]; then
log_message "INFO" "easytier" "添加防火墙规则 easytier_web 放行API端口 ${api_port}" "/tmp/easytierweb.log"
uci -q delete firewall.easytier_webapi
uci set firewall.easytier_webapi=rule
uci set firewall.easytier_webapi.name="easytier_webapi"
uci set firewall.easytier_webapi.target="ACCEPT"
uci set firewall.easytier_webapi.src="wan"
uci set firewall.easytier_webapi.proto="tcp"
uci set firewall.easytier_webapi.dest_port="$api_port"
uci set firewall.easytier_webapi.enabled="1"
fi
if [ -n "$html_port" ] && [ "$fw_api" = "1" ] && [ "$html_port" != "$api_port" ]; then
log_message "INFO" "easytier" "添加防火墙规则 easytier_web 放行html端口 ${html_port}" "/tmp/easytierweb.log"
uci -q delete firewall.easytier_webhtml
uci set firewall.easytier_webhtml=rule
uci set firewall.easytier_webhtml.name="easytier_webhtml"
uci set firewall.easytier_webhtml.target="ACCEPT"
uci set firewall.easytier_webhtml.src="wan"
uci set firewall.easytier_webhtml.proto="tcp"
uci set firewall.easytier_webhtml.dest_port="$html_port"
uci set firewall.easytier_webhtml.enabled="1"
fi
}
# 应用防火墙和网络配置更改(延迟后台执行)
apply_network_changes() {
if [ -n "$(uci changes network)" ] || [ -n "$(uci changes firewall)" ]; then
(
sleep 5
LOCK_FILE="/var/lock/easytier_reload.lock"
if [ ! -f "$LOCK_FILE" ]; then
touch "$LOCK_FILE"
[ -n "$(uci changes network)" ] && uci commit network && /etc/init.d/network reload >/dev/null 2>&1
[ -n "$(uci changes firewall)" ] && uci commit firewall && /etc/init.d/firewall reload >/dev/null 2>&1
rm -f "$LOCK_FILE"
fi
) &
fi
}
@@ -0,0 +1,89 @@
#!/bin/sh
# EasyTier Utility Functions
# Common helper functions for EasyTier init scripts
# 获取时区设置
get_tz() {
SET_TZ=""
[ -e "/etc/localtime" ] && return
for tzfile in /etc/TZ /var/etc/TZ; do
[ -e "$tzfile" ] || continue
tz="$(cat $tzfile 2>/dev/null)"
done
[ -z "$tz" ] && return
SET_TZ=$tz
}
# 获取 CPU 架构
get_cpu_arch() {
local cputype=$(uname -ms | tr ' ' '_' | tr '[A-Z]' '[a-z]')
local cpucore=""
[ -n "$(echo $cputype | grep -E 'linux.*armv.*')" ] && cpucore="arm"
[ -n "$(echo $cputype | grep -E 'linux.*armv7.*')" ] && [ -n "$(cat /proc/cpuinfo | grep vfp)" ] && cpucore="armv7"
[ -n "$(echo $cputype | grep -E 'linux.*aarch64.*|linux.*armv8.*')" ] && cpucore="aarch64"
[ -n "$(echo $cputype | grep -E 'linux.*86.*')" ] && cpucore="i386"
[ -n "$(echo $cputype | grep -E 'linux.*86_64.*')" ] && cpucore="x86_64"
if [ -n "$(echo $cputype | grep -E 'linux.*mips.*')" ]; then
local mipstype=$(echo -n I | hexdump -o 2>/dev/null | awk '{ print substr($2,6,1); exit}')
[ "$mipstype" = "0" ] && cpucore="mips" || cpucore="mipsel"
fi
echo "$cpucore"
}
# 统一日志记录
log_message() {
local level="$1"
local component="$2"
local message="$3"
local logfile="${4:-/tmp/easytier.log}"
echo "$(date '+%Y-%m-%d %H:%M:%S') ${component} : ${message}" >> "$logfile"
}
# 日志大小管理
manage_log_size() {
local logfile="$1"
local max_size_kb="${2:-5120}"
local keep_lines="${3:-500}"
while true; do
local log_size=$(ls -l "$logfile" 2>/dev/null | awk '{print int($5/1024)}')
if [ "${log_size:-0}" -gt "$max_size_kb" ]; then
tail -n "$keep_lines" "$logfile" > "${logfile}.tmp"
mv "${logfile}.tmp" "$logfile"
fi
sleep 300
done
}
# 获取可用磁盘空间 (KB)
get_available_space() {
local path="$1"
local size=$(df -k | awk '/\/overlay$/ {sub(/K$/, "", $4); print $4}')
[ -z "$size" ] && size=$(df -kP "$path" 2>/dev/null | awk 'NR==2 {print $(NF-2)}')
echo "${size:-0}"
}
# 安全执行命令并返回结果
safe_exec() {
local cmd="$1"
local result=$(eval "$cmd" 2>/dev/null)
echo "$result"
}
# 检查进程是否运行
is_process_running() {
local process_name="$1"
pgrep -f "$process_name" >/dev/null 2>&1
return $?
}
# 停止进程
stop_process() {
local process_name="$1"
ps | grep "$process_name" | grep -v grep | awk '{print $1}' | xargs kill >/dev/null 2>&1
ps | grep "$process_name" | grep -v grep | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
}
@@ -0,0 +1,12 @@
{
"luci-app-easytier": {
"description": "Grant UCI access for luci-app-easytier",
"read": {
"uci": [ "easytier" ],
"cgi-io": [ "upload" ]
},
"write": {
"uci": [ "easytier" ]
}
}
}
+5
View File
@@ -0,0 +1,5 @@
# EasyTier Version Configuration
# This file is the single source of truth for the EasyTier version
# Used by: easytier/Makefile, luci-app-easytier/Makefile, init.d/easytier
EASYTIER_VERSION=2.6.2