mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-11-15 12:42:55 +08:00
update 2026-10-27 07:31:06
This commit is contained in:
1 parent
197c60d843
commit
a4d6470641
4213 files changed
-1575003
No files matched your search
-272
@@ -1,272 +0,0 @@
|
||||
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: Generate package guide
|
||||
run: |
|
||||
PKG_DIR="bin/packages/${{ matrix.arch }}/packages_ci"
|
||||
EASYTIER_VERSION="$(awk -F= '/^EASYTIER_VERSION=/{print $2}' version.mk)"
|
||||
mkdir -p "$PKG_DIR"
|
||||
cat > "$PKG_DIR/README_PACKAGE.md" << EOF
|
||||
# EasyTier OpenWrt Packages / EasyTier OpenWrt 安装包说明
|
||||
|
||||
Build target / 构建目标: \`${{ matrix.arch }}-${{ matrix.sdk }}\`
|
||||
EasyTier version / EasyTier 版本: \`${EASYTIER_VERSION:-unknown}\`
|
||||
|
||||
## Package types / 包格式
|
||||
|
||||
- \`*.ipk\`: OpenWrt 22.03.x and other opkg-based firmware package. / 适用于 OpenWrt 22.03.x 等使用 opkg 的固件。
|
||||
- \`*.apk\`: OpenWrt SNAPSHOT and newer apk-based firmware package. / 适用于 OpenWrt SNAPSHOT 等使用 apk 的新版固件。
|
||||
|
||||
Use packages that match your device architecture and firmware package manager. Do not mix \`ipk\` and \`apk\` on the same system.
|
||||
请下载与设备 CPU 架构和固件包管理器匹配的包,不要在同一系统中混用 \`ipk\` 和 \`apk\`。
|
||||
|
||||
## What each package does / 每个包的作用
|
||||
|
||||
- \`easytier\`: EasyTier core binaries installed to \`/usr/bin\`, including \`easytier-core\`, \`easytier-cli\`, and the embedded web console binary when supported by the architecture. / EasyTier 核心程序包,安装到 \`/usr/bin\`,包含 \`easytier-core\`、\`easytier-cli\`,并在架构支持时集成自建 Web 控制台程序。
|
||||
- \`easytier-noweb\`: EasyTier core binaries installed to \`/usr/bin\`, without the embedded web console. Use this on devices with limited storage or when you do not need the self-hosted web console. / 不包含自建 Web 控制台的核心程序包,同样安装到 \`/usr/bin\`。适合存储空间不足或不需要自建 Web 控制台的设备。
|
||||
- \`luci-app-easytier\`: LuCI web interface, init script, UCI config, upload/download tools, and service management pages. / LuCI 管理界面,包含启动脚本、UCI 配置、上传/下载工具和服务管理页面。
|
||||
- \`luci-i18n-easytier-zh-cn\`: Simplified Chinese translation for the LuCI interface. / LuCI 界面的简体中文语言包。
|
||||
|
||||
## Recommended install order / 推荐安装顺序
|
||||
|
||||
1. Install one core package: \`easytier\` or \`easytier-noweb\`. / 先安装一个核心包:\`easytier\` 或 \`easytier-noweb\`。
|
||||
2. Install \`luci-app-easytier\`. / 再安装 \`luci-app-easytier\`。
|
||||
3. Install \`luci-i18n-easytier-zh-cn\` if you need Simplified Chinese. / 如需中文界面,再安装 \`luci-i18n-easytier-zh-cn\`。
|
||||
|
||||
If you need the EasyTier binaries in a custom directory, do not install \`easytier\` or \`easytier-noweb\`. Install only \`luci-app-easytier\` and the language package, then open LuCI -> VPN -> EasyTier -> Upload Program, set the program directory, save it, and upload the official EasyTier release files manually.
|
||||
如果需要把 EasyTier 程序安装到自定义目录,不要安装 \`easytier\` 或 \`easytier-noweb\`。只安装 \`luci-app-easytier\` 和语言包,然后进入 LuCI -> VPN -> EasyTier -> 上传程序,设置程序目录并保存,再手动上传官方 EasyTier Release 中的程序文件。
|
||||
|
||||
You can also use the online download function on the Upload Program page. This requires network access to GitHub. If GitHub access is slow or unavailable, configure acceleration mirror URLs before downloading.
|
||||
也可以在“上传程序”页面使用在线下载功能。该功能需要设备能正常访问 GitHub;如果访问慢或无法访问,请先配置加速镜像地址。
|
||||
|
||||
## IPK installation / IPK 安装方法
|
||||
|
||||
Upload the packages to \`/tmp\` on your OpenWrt device, then run:
|
||||
将安装包上传到 OpenWrt 设备的 \`/tmp\` 目录,然后执行:
|
||||
|
||||
\`\`\`sh
|
||||
opkg update
|
||||
opkg install /tmp/easytier_*.ipk
|
||||
opkg install /tmp/luci-app-easytier_*.ipk
|
||||
opkg install /tmp/luci-i18n-easytier-zh-cn_*.ipk
|
||||
\`\`\`
|
||||
|
||||
For the no-web core package:
|
||||
如果安装不含 Web 控制台的核心包:
|
||||
|
||||
\`\`\`sh
|
||||
opkg update
|
||||
opkg install /tmp/easytier-noweb_*.ipk
|
||||
opkg install /tmp/luci-app-easytier_*.ipk
|
||||
opkg install /tmp/luci-i18n-easytier-zh-cn_*.ipk
|
||||
\`\`\`
|
||||
|
||||
## APK installation / APK 安装方法
|
||||
|
||||
Upload the packages to \`/tmp\` on your OpenWrt device, then run:
|
||||
将安装包上传到 OpenWrt 设备的 \`/tmp\` 目录,然后执行:
|
||||
|
||||
\`\`\`sh
|
||||
apk add --allow-untrusted /tmp/easytier_*.apk
|
||||
apk add --allow-untrusted /tmp/luci-app-easytier_*.apk
|
||||
apk add --allow-untrusted /tmp/luci-i18n-easytier-zh-cn_*.apk
|
||||
\`\`\`
|
||||
|
||||
For the no-web core package:
|
||||
如果安装不含 Web 控制台的核心包:
|
||||
|
||||
\`\`\`sh
|
||||
apk add --allow-untrusted /tmp/easytier-noweb_*.apk
|
||||
apk add --allow-untrusted /tmp/luci-app-easytier_*.apk
|
||||
apk add --allow-untrusted /tmp/luci-i18n-easytier-zh-cn_*.apk
|
||||
\`\`\`
|
||||
|
||||
## Install LuCI only and upload binaries manually / 只安装 LuCI 并手动上传程序
|
||||
|
||||
IPK:
|
||||
|
||||
\`\`\`sh
|
||||
opkg update
|
||||
opkg install /tmp/luci-app-easytier_*.ipk
|
||||
opkg install /tmp/luci-i18n-easytier-zh-cn_*.ipk
|
||||
\`\`\`
|
||||
|
||||
APK:
|
||||
|
||||
\`\`\`sh
|
||||
apk add --allow-untrusted /tmp/luci-app-easytier_*.apk
|
||||
apk add --allow-untrusted /tmp/luci-i18n-easytier-zh-cn_*.apk
|
||||
\`\`\`
|
||||
|
||||
Then download the matching EasyTier Linux release from:
|
||||
然后从以下地址下载匹配架构的 EasyTier Linux 官方 Release 包:
|
||||
|
||||
https://github.com/EasyTier/EasyTier/releases
|
||||
|
||||
In LuCI, go to VPN -> EasyTier -> Upload Program, set the program directory, save it, and upload \`easytier-core\`, \`easytier-cli\`, and optionally \`easytier-web-embed\`.
|
||||
在 LuCI 中进入 VPN -> EasyTier -> 上传程序,设置程序目录并保存,然后上传 \`easytier-core\`、\`easytier-cli\`,以及可选的 \`easytier-web-embed\`。
|
||||
|
||||
## After installation / 安装后
|
||||
|
||||
- Refresh LuCI or log in again. / 刷新 LuCI 页面或重新登录。
|
||||
- Open VPN -> EasyTier. / 打开 VPN -> EasyTier。
|
||||
- Configure network name, network secret, peers, and other options, then enable the service. / 配置网络名称、网络密钥、节点等参数,然后启用服务。
|
||||
- Check logs on the EasyTier log page or by running \`logread | grep -i easytier\`. / 可在 EasyTier 日志页面查看日志,或执行 \`logread | grep -i easytier\`。
|
||||
|
||||
## Troubleshooting / 问题处理
|
||||
|
||||
- Wrong architecture or package format: download the zip that matches your device architecture and firmware version. / 架构或包格式错误:请下载与设备架构和固件版本匹配的压缩包。
|
||||
- Missing TUN support: install or enable \`kmod-tun\`. / 缺少 TUN 支持:安装或启用 \`kmod-tun\`。
|
||||
- LuCI menu not visible: refresh the browser, clear LuCI cache, or run \`rm -f /tmp/luci-indexcache /tmp/luci-modulecache/*\` and re-login. / LuCI 菜单不显示:刷新浏览器、清理 LuCI 缓存,或执行 \`rm -f /tmp/luci-indexcache /tmp/luci-modulecache/*\` 后重新登录。
|
||||
- Online download fails: check DNS, gateway, and GitHub connectivity; configure acceleration mirror URLs or upload files manually. / 在线下载失败:检查 DNS、网关和 GitHub 连通性;配置加速镜像地址或手动上传程序。
|
||||
- Not enough storage: install \`easytier-noweb\`, or install only LuCI and upload binaries to a larger custom directory. / 存储空间不足:安装 \`easytier-noweb\`,或只安装 LuCI 并把程序上传到容量更大的自定义目录。
|
||||
- Service does not start: verify the program path, executable permission, and configuration; then restart with \`/etc/init.d/easytier restart\`. / 服务无法启动:检查程序路径、可执行权限和配置,然后执行 \`/etc/init.d/easytier restart\`。
|
||||
|
||||
EOF
|
||||
|
||||
- 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
|
||||
shopt -s nullglob
|
||||
for dir in artifacts/*; do
|
||||
name=$(basename "$dir")
|
||||
files=("$dir"/*.ipk "$dir"/*.apk "$dir"/*.md)
|
||||
if [ ${#files[@]} -gt 0 ]; then
|
||||
zip -j "release/EasyTier-${{ env.TAG }}-${name}.zip" "${files[@]}"
|
||||
fi
|
||||
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: |
|
||||
|
||||
> ### 
|
||||
|
||||
- 其中 **`22.03.7`** 里面是ipk安装包 **`SNAPSHOT`** 里面是apk安装包
|
||||
|
||||
- **`*.apk` 是OpenWrt新版的APK包** 对于apk包跳过证书命令
|
||||
`apk add --allow-untrusted luci-app-easytier.apk`
|
||||
|
||||
- 每个压缩包内包含 **`README_PACKAGE.md`** 中英双语说明,包含各包作用、安装顺序、IPK/APK安装方法和常见问题处理
|
||||
|
||||
${{ github.event.inputs.text }}
|
||||
|
||||
tag_name: ${{ env.TAG }}
|
||||
files: release/*.zip
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||

|
||||
|
||||
# 开发指南
|
||||
|
||||
[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/)
|
||||
@@ -1,396 +0,0 @@
|
||||

|
||||
|
||||
# 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/)
|
||||
@@ -1,190 +0,0 @@
|
||||
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.
|
||||
@@ -1,268 +0,0 @@
|
||||

|
||||
|
||||
[English](README_EN.md) | 简体中文
|
||||
|
||||
[](LICENSE)
|
||||
[](https://openwrt.org)
|
||||
[](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!
|
||||
|
||||
---
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||

|
||||
|
||||
English | [简体中文](README.md)
|
||||
|
||||
[](LICENSE)
|
||||
[](https://openwrt.org)
|
||||
[](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!
|
||||
|
||||
---
|
||||
@@ -1,74 +0,0 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
|
||||
|
||||
PKG_NAME:=easytier-noweb
|
||||
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.7.0)
|
||||
|
||||
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 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
|
||||
$(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
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,$(PKG_NAME)))
|
||||
@@ -1,88 +0,0 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
-include $(dir $(lastword $(MAKEFILE_LIST)))../version.mk
|
||||
|
||||
PKG_NAME:=easytier
|
||||
PKG_VERSION:=$(or $(EASYTIER_VERSION),2.7.0)
|
||||
|
||||
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)))
|
||||
@@ -1,61 +0,0 @@
|
||||
#
|
||||
# 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.7.0)
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_PO_VERSION:=$(PKG_VERSION)
|
||||
|
||||
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
@@ -1,580 +0,0 @@
|
||||
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/assistant\" target=\"_blank\">"
|
||||
.. "https://easytier.cn/assistant</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>"
|
||||
.. "Since EasyTier 2.7.0 a full URL is required, the token-only shorthand is no longer supported<br>"
|
||||
.. "Self-hosted Web server, use the format: udp://server_address:22020/username<br>"
|
||||
.. "Official Web server, use the format: udp://config-server.easytier.cn:22020/username<br>"
|
||||
.. "Official Web Console: <a href='https://easytier.cn/web'>easytier.cn/web</a>"))
|
||||
web_config.placeholder = "udp://config-server.easytier.cn:22020/my-token"
|
||||
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: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 Listen Address"),
|
||||
translate("WireGuard VPN portal listen address, allowing other VPN clients to connect.<br>"
|
||||
.. "Since EasyTier 2.7.0 only wg://HOST:PORT is accepted; the client network can no longer be "
|
||||
.. "appended to the URL, please add each client in \"VPN Portal Clients\" below "
|
||||
.. "(--vpn-portal parameter)"))
|
||||
vpn_portal.placeholder = "wg://0.0.0.0:11011"
|
||||
vpn_portal.validate = function(self, value, section)
|
||||
value = value and tostring(value):match("^%s*(.-)%s*$") or ""
|
||||
if value == "" then return value end
|
||||
-- EasyTier 2.7.0 起不再允许把客户端网段写在 URL 里,这里提前拦截,避免核心启动失败
|
||||
if not (value:match("^wg://%d+%.%d+%.%d+%.%d+:%d+$") or value:match("^wg://%[[%x:]+%]:%d+$")) then
|
||||
return nil, translate("Only wg://HOST:PORT is accepted since EasyTier 2.7.0, please fill the client network into \"VPN Portal Clients\"")
|
||||
end
|
||||
return value
|
||||
end
|
||||
vpn_portal:depends("etcmd", "etcmd")
|
||||
|
||||
vpn_portal_clients = s:taboption("privacy", DynamicList, "vpn_portal_clients", translate("VPN Portal Clients"),
|
||||
translate("One WireGuard client per entry in the form NAME=CIDR, for example client1=10.14.14.1/24.<br>"
|
||||
.. "NAME may contain letters, digits and dashes only; the address must be a usable host address "
|
||||
.. "inside the client network. (--vpn-portal-client parameter)"))
|
||||
vpn_portal_clients.placeholder = "client1=10.14.14.1/24"
|
||||
vpn_portal_clients.validate = function(self, value, section)
|
||||
if type(value) == "table" then
|
||||
local i
|
||||
for i = 1, #value do
|
||||
local item = value[i] and tostring(value[i]):match("^%s*(.-)%s*$") or ""
|
||||
if item ~= "" then
|
||||
-- 与 easytier-core 的校验保持一致:名称只能是字母数字与短横线,地址必须是 主机/前缀
|
||||
if not item:match("^[%w][%w%-]*=[%d%.]+/%d+$") then
|
||||
return nil, translate("Invalid client, expected NAME=CIDR, for example client1=10.14.14.1/24")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
vpn_portal_clients:depends("etcmd", "etcmd")
|
||||
|
||||
vpn_portal_private_key = s:taboption("privacy", Value, "vpn_portal_private_key", translate("VPN Portal Private Key"),
|
||||
translate("Base64 encoded 32-byte WireGuard private key of the portal.<br>"
|
||||
.. "Required since EasyTier 2.7.0, the portal will not start without it. "
|
||||
.. "It is handed to easytier-core through an environment variable so that it never shows up in the process list. "
|
||||
.. "(--vpn-portal-private-key parameter)"))
|
||||
vpn_portal_private_key.password = true
|
||||
vpn_portal_private_key: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_upnp = s:taboption("privacy", Flag, "disable_upnp", translate("Disable UPnP"),
|
||||
translate("EasyTier 2.7.0 tries to map listener ports through UPnP/NAT-PMP by default. "
|
||||
.. "Enable this to turn the automatic port mapping off. (--disable-upnp parameter)"))
|
||||
disable_upnp:depends("etcmd", "etcmd")
|
||||
|
||||
stun_servers = s:taboption("privacy", DynamicList, "stun_servers", translate("STUN Servers"),
|
||||
translate("Custom UDP STUN servers used for NAT type detection and hole punching. "
|
||||
.. "Leave empty to use the built-in defaults. (--stun-servers parameter)"))
|
||||
stun_servers.placeholder = "stun.miwifi.com:3478"
|
||||
stun_servers:depends("etcmd", "etcmd")
|
||||
|
||||
tcp_stun_servers = s:taboption("privacy", DynamicList, "tcp_stun_servers", translate("STUN Servers (TCP)"),
|
||||
translate("Custom TCP STUN servers used for NAT type detection and hole punching. "
|
||||
.. "Leave empty to use the built-in defaults. (--tcp-stun-servers parameter)"))
|
||||
tcp_stun_servers.placeholder = "stun.miwifi.com:3478"
|
||||
tcp_stun_servers: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")
|
||||
|
||||
lazy_p2p = s:taboption("privacy", Flag, "lazy_p2p", translate("Lazy P2P"),
|
||||
translate("Only try to establish P2P connections when traffic actually needs to be forwarded. "
|
||||
.. "(--lazy-p2p parameter)"))
|
||||
lazy_p2p:depends("etcmd", "etcmd")
|
||||
|
||||
need_p2p = s:taboption("privacy", Flag, "need_p2p", translate("Need P2P"),
|
||||
translate("Require a P2P connection before forwarding traffic through relays. (--need-p2p parameter)"))
|
||||
need_p2p: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")
|
||||
|
||||
disable_relay_quic = s:taboption("privacy", Flag, "disable_relay_quic", translate("Disable relay quic"),
|
||||
translate("If true, disable relaying quic packets, avoiding consuming too much bandwidth. (--disable-relay-quic parameter)"))
|
||||
disable_relay_quic:depends("etcmd", "etcmd")
|
||||
|
||||
relay_quic = s:taboption("privacy", Flag, "relay_quic", translate("Relay foreign network quic"),
|
||||
translate("If true, allow relaying quic packets from foreign networks. (--enable-relay-foreign-network-quic parameter)"))
|
||||
relay_quic: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")
|
||||
|
||||
tld_dns_zone = s:taboption("privacy", Value, "tld_dns_zone", translate("Magic DNS Zone"),
|
||||
translate("Top level DNS zone used by Magic DNS, default is et.net. Only takes effect when Magic DNS is enabled. "
|
||||
.. "(--tld-dns-zone parameter)"))
|
||||
tld_dns_zone.placeholder = "et.net."
|
||||
tld_dns_zone:depends("accept_dns", "1")
|
||||
|
||||
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")
|
||||
|
||||
instance_recv_bps_limit = s:taboption("privacy", Value, "instance_recv_bps_limit", translate("Instance Receive Rate Limit"),
|
||||
translate("the maximum inbound bps limit for this network instance, default is no limit. "
|
||||
.. "unit: BPS (bytes per second). (--instance-recv-bps-limit parameter)"))
|
||||
instance_recv_bps_limit:depends("etcmd", "etcmd")
|
||||
|
||||
socket_mark = s:taboption("privacy", Value, "socket_mark", translate("Socket Mark"),
|
||||
translate("Set SO_MARK on all underlay sockets, Linux only and requires CAP_NET_ADMIN. "
|
||||
.. "Usually used together with policy routing. (--socket-mark parameter)"))
|
||||
socket_mark.datatype = "range(0,4294967295)"
|
||||
socket_mark:depends("etcmd", "etcmd")
|
||||
|
||||
extra_args = s:taboption("privacy", DynamicList, "extra_args", translate("Extra Parameters"),
|
||||
translate("Additional command-line arguments passed to the backend process"))
|
||||
extra_args.placeholder = "--tcp-whitelist=80"
|
||||
extra_args.validate = function(self, value, section)
|
||||
if type(value) == "table" then
|
||||
local seen = {}
|
||||
local i
|
||||
for i = 1, #value do
|
||||
local arg = value[i]
|
||||
if arg then
|
||||
arg = tostring(arg):match("^%s*(.-)%s*$")
|
||||
if arg ~= "" then
|
||||
if seen[arg] then
|
||||
return nil, translate("Duplicate extra parameter") .. ": " .. arg
|
||||
end
|
||||
seen[arg] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
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
|
||||
@@ -1,19 +0,0 @@
|
||||
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> "
|
||||
.. "<a href=\"http://easytier.cn\" target=\"_blank\">Official Documentation</a> "
|
||||
.. "<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> ")
|
||||
.. "<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> "
|
||||
.. "<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> "
|
||||
.. "<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
@@ -1,730 +0,0 @@
|
||||
<%
|
||||
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-wiki-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
margin-right: 8px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.log-wiki-link img {
|
||||
display: block;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
.log-select {
|
||||
box-sizing: border-box;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
height: 34px;
|
||||
padding: 6px 24px 6px 10px;
|
||||
border: 1px solid var(--log-border);
|
||||
background: var(--log-bg);
|
||||
color: var(--log-text);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 1.2;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
flex: 0 0 auto;
|
||||
max-width: 136px;
|
||||
}
|
||||
|
||||
.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 {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
height: 34px;
|
||||
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);
|
||||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.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-switch {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 34px;
|
||||
padding: 0 4px;
|
||||
color: var(--log-text);
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.log-switch input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.log-switch-track {
|
||||
position: relative;
|
||||
width: 42px;
|
||||
height: 22px;
|
||||
border-radius: 999px;
|
||||
background: var(--log-border);
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.18);
|
||||
transition: background 0.2s ease, box-shadow 0.2s ease;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.log-switch-track::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.28);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.log-switch input:checked + .log-switch-track {
|
||||
background: var(--tab-active);
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.12), 0 0 0 2px rgba(33,150,243,0.16);
|
||||
}
|
||||
|
||||
.log-switch input:checked + .log-switch-track::after {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.log-switch-text {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.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: 10px 12px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.log-tabs {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.log-tab {
|
||||
padding: 7px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.log-controls {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.log-wiki-link {
|
||||
height: 30px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.log-wiki-link img {
|
||||
max-width: 96px;
|
||||
}
|
||||
|
||||
.log-select {
|
||||
height: 28px;
|
||||
max-width: 96px;
|
||||
padding: 3px 18px 3px 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1.1;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
height: 60vh;
|
||||
min-height: 320px;
|
||||
font-size: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.log-btn {
|
||||
flex: 0 0 auto;
|
||||
height: 28px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
overflow-wrap: normal;
|
||||
}
|
||||
|
||||
.log-switch {
|
||||
height: 28px;
|
||||
gap: 5px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.log-switch-track {
|
||||
width: 34px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.log-switch-track::after {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.log-switch input:checked + .log-switch-track::after {
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
.log-switch-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.log-footer {
|
||||
padding: 6px 12px;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 420px) {
|
||||
.log-container {
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.log-controls {
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.log-wiki-link img {
|
||||
max-width: 82px;
|
||||
}
|
||||
|
||||
.log-select {
|
||||
max-width: 88px;
|
||||
}
|
||||
|
||||
.log-btn {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.log-switch-text {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</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>
|
||||
<a class="log-wiki-link" href="https://deepwiki.com/EasyTier/EasyTier" target="_blank">
|
||||
<img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki">
|
||||
</a>
|
||||
</div>
|
||||
<div class="log-controls">
|
||||
<label class="log-switch" id="auto_refresh_switch" title="<%=translate("Auto Refresh")%>">
|
||||
<span class="log-switch-text"><%=translate("Auto Refresh")%></span>
|
||||
<input type="checkbox" id="auto_refresh_toggle" checked onchange="toggleAutoRefresh(true)">
|
||||
<span class="log-switch-track" aria-hidden="true"></span>
|
||||
</label>
|
||||
<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" 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(refreshNow) {
|
||||
var toggle = document.getElementById('auto_refresh_toggle');
|
||||
var wrapper = document.getElementById('auto_refresh_switch');
|
||||
autoRefreshEnabled = toggle ? toggle.checked : !autoRefreshEnabled;
|
||||
|
||||
if (autoRefreshEnabled) {
|
||||
if (wrapper) wrapper.title = '<%=translate("Stop Auto Refresh")%>';
|
||||
if (refreshNow) refreshLog();
|
||||
} else {
|
||||
if (wrapper) wrapper.title = '<%=translate("Start Auto Refresh")%>';
|
||||
}
|
||||
}
|
||||
|
||||
toggleAutoRefresh(false);
|
||||
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 +0,0 @@
|
||||
./zh_Hans
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,9 +0,0 @@
|
||||
|
||||
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'
|
||||
option web_enabled '0'
|
||||
option webbin '/usr/bin/easytier-web'
|
||||
@@ -1,31 +0,0 @@
|
||||
instance_name = "default"
|
||||
instance_id = "f9f05421-7c6b-47d6-a67d-4d58d49f51d8"
|
||||
dhcp = true
|
||||
listeners = []
|
||||
exit_nodes = []
|
||||
# 注意:顶层配置结构里没有 rpc_portal 字段,该行会被 easytier-core 静默忽略。
|
||||
# RPC 端口只能通过命令行 -r 指定(本插件的“RPC 端口”选项),保持默认即可。
|
||||
rpc_portal = "0.0.0.0:15888"
|
||||
|
||||
[network_identity]
|
||||
network_name = "mynetwork"
|
||||
network_secret = "mysecret"
|
||||
|
||||
[[peer]]
|
||||
uri = "tcp://public.easytier.top:11010"
|
||||
|
||||
# EasyTier 2.7.0 起 WireGuard 门户写法(默认关闭,需要时取消注释)
|
||||
# 旧版把客户端网段写在 wireguard_listen 之外的 client_cidr 键已不再支持,会导致配置文件解析失败。
|
||||
# 门户私钥为 base64 编码的 32 字节,2.7.0 起必须填写,否则门户监听不会启动。
|
||||
# [vpn_portal_config]
|
||||
# enabled = true
|
||||
# wireguard_listen = "0.0.0.0:51820"
|
||||
# wireguard_private_key = ""
|
||||
#
|
||||
# [[vpn_portal_config.clients]]
|
||||
# name = "client1"
|
||||
# virtual_ip = "10.14.14.1/24"
|
||||
# groups = []
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,13 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,151 +0,0 @@
|
||||
#!/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.7.0"
|
||||
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
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
#!/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"
|
||||
# 不再自动放行 SOCKS5 端口到 WAN —— 无认证 SOCKS5 代理暴露给公网有严重安全风险
|
||||
# 如需恢复自动放行,去掉下面一行行首的 # 即可
|
||||
# [ -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
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
#!/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
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"luci-app-easytier": {
|
||||
"description": "Grant UCI access for luci-app-easytier",
|
||||
"read": {
|
||||
"uci": [ "easytier" ],
|
||||
"cgi-io": [ "upload" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "easytier" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
# 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.7.0
|
||||
Reference in new issue
Block a user