Files
GoTunnel/PLUGINS.md
Flik 4d2a2a7117
All checks were successful
Build Multi-Platform Binaries / build-frontend (push) Successful in 30s
Build Multi-Platform Binaries / build-binaries (amd64, darwin, server, false) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 48s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 1m23s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 56s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 58s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 52s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 1m42s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m19s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 54s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 2m3s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m1s
update
2025-12-29 23:08:15 +08:00

561 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GoTunnel 插件开发指南
本文档介绍如何为 GoTunnel 开发 JS 插件。
## 目录
- [快速开始](#快速开始)
- [插件结构](#插件结构)
- [API 参考](#api-参考)
- [示例插件](#示例插件)
- [插件签名](#插件签名)
- [发布到商店](#发布到商店)
---
## 快速开始
### 最小插件示例
```javascript
// 必须:定义插件元数据
function metadata() {
return {
name: "my-plugin",
version: "1.0.0",
type: "app",
description: "My first plugin",
author: "Your Name"
};
}
// 可选:插件启动时调用
function start() {
log("Plugin started");
}
// 必须:处理连接
function handleConn(conn) {
// 处理连接逻辑
conn.Close();
}
// 可选:插件停止时调用
function stop() {
log("Plugin stopped");
}
```
---
## 插件结构
### 生命周期函数
| 函数 | 必须 | 说明 |
|------|------|------|
| `metadata()` | 否 | 返回插件元数据,不定义则使用默认值 |
| `start()` | 否 | 插件启动时调用 |
| `handleConn(conn)` | 是 | 处理每个连接 |
| `stop()` | 否 | 插件停止时调用 |
### 元数据字段
```javascript
function metadata() {
return {
name: "plugin-name", // 插件名称
version: "1.0.0", // 版本号
type: "app", // 类型: "app" 或 "proxy"
run_at: "client", // 运行位置: "client" 或 "server"
description: "描述", // 插件描述
author: "作者" // 作者名称
};
}
```
---
## API 参考
### 基础 API
#### `log(message)`
输出日志信息。
```javascript
log("Hello, World!");
// 输出: [JS:plugin-name] Hello, World!
```
#### `config(key)`
获取插件配置值。
```javascript
var port = config("port");
var host = config("host") || "127.0.0.1";
```
---
### 连接 API (conn)
`handleConn` 函数接收的 `conn` 对象提供以下方法:
#### `conn.Read(size)`
读取数据,返回字节数组,失败返回 `null`
```javascript
var data = conn.Read(1024);
if (data) {
log("Received " + data.length + " bytes");
}
```
#### `conn.Write(data)`
写入数据,返回写入的字节数。
```javascript
var written = conn.Write(data);
log("Wrote " + written + " bytes");
```
#### `conn.Close()`
关闭连接。
```javascript
conn.Close();
```
---
### 文件系统 API (fs)
所有文件操作都在沙箱中执行,有路径和大小限制。
#### `fs.readFile(path)`
读取文件内容。
```javascript
var result = fs.readFile("/path/to/file.txt");
if (result.error) {
log("Error: " + result.error);
} else {
log("Content: " + result.data);
}
```
#### `fs.writeFile(path, content)`
写入文件内容。
```javascript
var result = fs.writeFile("/path/to/file.txt", "Hello");
if (result.ok) {
log("File written");
} else {
log("Error: " + result.error);
}
```
#### `fs.readDir(path)`
读取目录内容。
```javascript
var result = fs.readDir("/path/to/dir");
if (!result.error) {
for (var i = 0; i < result.entries.length; i++) {
var entry = result.entries[i];
log(entry.name + " - " + (entry.isDir ? "DIR" : entry.size + " bytes"));
}
}
```
#### `fs.stat(path)`
获取文件信息。
```javascript
var result = fs.stat("/path/to/file");
if (!result.error) {
log("Name: " + result.name);
log("Size: " + result.size);
log("IsDir: " + result.isDir);
log("ModTime: " + result.modTime);
}
```
#### `fs.exists(path)`
检查文件是否存在。
```javascript
var result = fs.exists("/path/to/file");
if (result.exists) {
log("File exists");
}
```
#### `fs.mkdir(path)`
创建目录。
```javascript
var result = fs.mkdir("/path/to/new/dir");
if (result.ok) {
log("Directory created");
}
```
#### `fs.remove(path)`
删除文件或目录。
```javascript
var result = fs.remove("/path/to/file");
if (result.ok) {
log("Removed");
}
```
---
### HTTP API (http)
用于构建简单的 HTTP 服务。
#### `http.serve(conn, handler)`
处理 HTTP 请求。
```javascript
function handleConn(conn) {
http.serve(conn, function(req) {
return {
status: 200,
contentType: "application/json",
body: http.json({ message: "Hello", path: req.path })
};
});
}
```
**请求对象 (req):**
| 字段 | 类型 | 说明 |
|------|------|------|
| `method` | string | HTTP 方法 (GET, POST, etc.) |
| `path` | string | 请求路径 |
| `body` | string | 请求体 |
**响应对象:**
| 字段 | 类型 | 说明 |
|------|------|------|
| `status` | number | HTTP 状态码 (默认 200) |
| `contentType` | string | Content-Type (默认 application/json) |
| `body` | string | 响应体 |
#### `http.json(data)`
将对象序列化为 JSON 字符串。
```javascript
var jsonStr = http.json({ name: "test", value: 123 });
// 返回: '{"name":"test","value":123}'
```
#### `http.sendFile(conn, filePath)`
发送文件作为 HTTP 响应。
```javascript
function handleConn(conn) {
http.sendFile(conn, "/path/to/index.html");
}
```
---
## 示例插件
### Echo 服务
```javascript
function metadata() {
return {
name: "echo",
version: "1.0.0",
type: "app",
description: "Echo back received data"
};
}
function handleConn(conn) {
while (true) {
var data = conn.Read(4096);
if (!data || data.length === 0) {
break;
}
conn.Write(data);
}
conn.Close();
}
```
### HTTP 文件服务器
```javascript
function metadata() {
return {
name: "file-server",
version: "1.0.0",
type: "app",
description: "Simple HTTP file server"
};
}
var rootDir = "";
function start() {
rootDir = config("root") || "/tmp";
log("Serving files from: " + rootDir);
}
function handleConn(conn) {
http.serve(conn, function(req) {
if (req.method === "GET") {
var filePath = rootDir + req.path;
if (req.path === "/") {
filePath = rootDir + "/index.html";
}
var stat = fs.stat(filePath);
if (stat.error) {
return { status: 404, body: "Not Found" };
}
if (stat.isDir) {
return listDirectory(filePath);
}
var file = fs.readFile(filePath);
if (file.error) {
return { status: 500, body: file.error };
}
return {
status: 200,
contentType: "text/html",
body: file.data
};
}
return { status: 405, body: "Method Not Allowed" };
});
}
function listDirectory(path) {
var result = fs.readDir(path);
if (result.error) {
return { status: 500, body: result.error };
}
var html = "<html><body><h1>Directory Listing</h1><ul>";
for (var i = 0; i < result.entries.length; i++) {
var e = result.entries[i];
html += "<li><a href='" + e.name + "'>" + e.name + "</a></li>";
}
html += "</ul></body></html>";
return { status: 200, contentType: "text/html", body: html };
}
```
### JSON API 服务
```javascript
function metadata() {
return {
name: "api-server",
version: "1.0.0",
type: "app",
description: "JSON API server"
};
}
var counter = 0;
function handleConn(conn) {
http.serve(conn, function(req) {
if (req.path === "/api/status") {
return {
status: 200,
body: http.json({
status: "ok",
counter: counter++,
timestamp: Date.now()
})
};
}
if (req.path === "/api/echo" && req.method === "POST") {
return {
status: 200,
body: http.json({
received: req.body
})
};
}
return {
status: 404,
body: http.json({ error: "Not Found" })
};
});
}
```
---
## 插件签名
为了安全JS 插件需要官方签名才能运行。
### 签名格式
签名文件 (`.sig`) 包含 Base64 编码的签名数据:
```json
{
"payload": {
"name": "plugin-name",
"version": "1.0.0",
"checksum": "sha256-hash",
"key_id": "official-v1"
},
"signature": "base64-signature"
}
```
### 获取签名
1. 提交插件到官方仓库
2. 通过审核后获得签名
3.`.js``.sig` 文件一起分发
---
## 发布到商店
### 商店 JSON 格式
插件商店使用 `store.json` 文件索引所有插件:
```json
[
{
"name": "echo",
"version": "1.0.0",
"type": "app",
"description": "Echo service plugin",
"author": "GoTunnel",
"icon": "https://example.com/icon.png",
"download_url": "https://example.com/plugins/echo.js"
}
]
```
### 提交流程
1. Fork 官方插件仓库
2. 添加插件文件到 `plugins/` 目录
3. 更新 `store.json`
4. 提交 Pull Request
5. 等待审核和签名
---
## 沙箱限制
为了安全JS 插件运行在沙箱环境中:
| 限制项 | 默认值 |
|--------|--------|
| 最大读取文件大小 | 10 MB |
| 最大写入文件大小 | 10 MB |
| 允许读取路径 | 插件数据目录 |
| 允许写入路径 | 插件数据目录 |
---
## 调试技巧
### 日志输出
使用 `log()` 函数输出调试信息:
```javascript
log("Debug: variable = " + JSON.stringify(variable));
```
### 错误处理
始终检查 API 返回的错误:
```javascript
var result = fs.readFile(path);
if (result.error) {
log("Error reading file: " + result.error);
return;
}
```
### 配置测试
在服务端配置中测试插件:
```yaml
js_plugins:
- name: my-plugin
path: /path/to/my-plugin.js
sig_path: /path/to/my-plugin.js.sig
config:
debug: "true"
port: "8080"
```
---
## 常见问题
**Q: 插件无法加载?**
A: 检查签名文件是否存在且有效。
**Q: 文件操作失败?**
A: 确认路径在沙箱允许范围内。
**Q: 如何获取客户端 IP**
A: 目前 API 不支持,计划在后续版本添加。
---
## 更新日志
### v1.0.0
- 初始版本
- 支持基础 API: log, config
- 支持连接 API: Read, Write, Close
- 支持文件系统 API: fs.*
- 支持 HTTP API: http.*