Files
GoTunnel/PLUGINS.md
Flik 2f98e1ac7d
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 1m16s
Build Multi-Platform Binaries / build-binaries (amd64, linux, client, true) (push) Successful in 1m4s
Build Multi-Platform Binaries / build-binaries (amd64, linux, server, true) (push) Successful in 2m29s
Build Multi-Platform Binaries / build-binaries (amd64, windows, client, true) (push) Successful in 54s
Build Multi-Platform Binaries / build-binaries (amd64, windows, server, true) (push) Successful in 2m35s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, client, true) (push) Successful in 55s
Build Multi-Platform Binaries / build-binaries (arm, 7, linux, server, true) (push) Successful in 2m21s
Build Multi-Platform Binaries / build-binaries (arm64, darwin, server, false) (push) Successful in 1m35s
Build Multi-Platform Binaries / build-binaries (arm64, linux, client, true) (push) Successful in 1m1s
Build Multi-Platform Binaries / build-binaries (arm64, linux, server, true) (push) Successful in 1m55s
Build Multi-Platform Binaries / build-binaries (arm64, windows, server, false) (push) Successful in 1m39s
feat(logging): implement client log streaming and management
- Added log streaming functionality for clients, allowing real-time log access via SSE.
- Introduced LogHandler to manage log streaming requests and responses.
- Implemented LogSessionManager to handle active log sessions and listeners.
- Enhanced protocol with log-related message types and structures.
- Created Logger for client-side logging, supporting various log levels and file output.
- Developed LogViewer component for the web interface to display and filter logs.
- Updated API to support log stream creation and management.
- Added support for querying logs by level and searching through log messages.
2026-01-03 16:19:52 +08:00

12 KiB
Raw Blame History

GoTunnel 插件开发指南

本文档介绍如何为 GoTunnel 开发 JS 插件。JS 插件基于 goja 运行时,运行在客户端上。

目录


快速开始

最小插件示例

// 必须:定义插件元数据
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() 插件停止时调用

元数据字段

function metadata() {
    return {
        name: "plugin-name",      // 插件名称
        version: "1.0.0",         // 版本号
        type: "app",              // 类型: "app" (应用插件)
        description: "描述",      // 插件描述
        author: "作者"            // 作者名称
    };
}

API 参考

基础 API

log(message)

输出日志信息。

log("Hello, World!");
// 输出: [JS:plugin-name] Hello, World!

config(key)

获取插件配置值。

var port = config("port");
var host = config("host") || "127.0.0.1";

连接 API (conn)

handleConn 函数接收的 conn 对象提供以下方法:

conn.Read(size)

读取数据,返回字节数组,失败返回 null

var data = conn.Read(1024);
if (data) {
    log("Received " + data.length + " bytes");
}

conn.Write(data)

写入数据,返回写入的字节数。

var written = conn.Write(data);
log("Wrote " + written + " bytes");

conn.Close()

关闭连接。

conn.Close();

文件系统 API (fs)

所有文件操作都在沙箱中执行,有路径和大小限制。

fs.readFile(path)

读取文件内容。

var result = fs.readFile("/path/to/file.txt");
if (result.error) {
    log("Error: " + result.error);
} else {
    log("Content: " + result.data);
}

fs.writeFile(path, content)

写入文件内容。

var result = fs.writeFile("/path/to/file.txt", "Hello");
if (result.ok) {
    log("File written");
} else {
    log("Error: " + result.error);
}

fs.readDir(path)

读取目录内容。

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)

获取文件信息。

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)

检查文件是否存在。

var result = fs.exists("/path/to/file");
if (result.exists) {
    log("File exists");
}

fs.mkdir(path)

创建目录。

var result = fs.mkdir("/path/to/new/dir");
if (result.ok) {
    log("Directory created");
}

fs.remove(path)

删除文件或目录。

var result = fs.remove("/path/to/file");
if (result.ok) {
    log("Removed");
}

HTTP API (http)

用于构建简单的 HTTP 服务。

http.serve(conn, handler)

处理 HTTP 请求。

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 字符串。

var jsonStr = http.json({ name: "test", value: 123 });
// 返回: '{"name":"test","value":123}'

http.sendFile(conn, filePath)

发送文件作为 HTTP 响应。

function handleConn(conn) {
    http.sendFile(conn, "/path/to/index.html");
}

增强 API (Enhanced APIs)

GoTunnel v2.0+ 提供了更多强大的 API 能力。

logger (日志)

推荐使用结构化日志替代简单的 log()

  • logger.info(msg)
  • logger.warn(msg)
  • logger.error(msg)
logger.info("Server started");
logger.error("Connection failed");

config (配置)

增强的配置获取方式。

  • config.get(key): 获取配置值
  • config.getAll(): 获取所有配置
var all = config.getAll();
var port = config.get("port");

storage (持久化存储)

简单的 Key-Value 存储,数据保存在客户端本地。

  • storage.get(key, default)
  • storage.set(key, value)
  • storage.delete(key)
  • storage.keys()
storage.set("last_run", Date.now());
var last = storage.get("last_run", 0);

event (事件总线)

插件内部或插件间的事件通信。

  • event.on(name, callback)
  • event.emit(name, data)
  • event.off(name)
event.on("user_login", function(user) {
    logger.info("User logged in: " + user);
});
event.emit("user_login", "admin");

request (HTTP 请求)

发起外部 HTTP 请求。

  • request.get(url)
  • request.post(url, contentType, body)
var res = request.get("https://api.ipify.org");
logger.info("My IP: " + res.body);

notify (通知)

发送系统通知。

  • notify.send(title, message)
notify.send("Download Complete", "File saved to disk");

示例插件

Echo 服务

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 文件服务器

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 服务

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 编码的签名数据:

{
  "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 文件索引所有插件:

[
  {
    "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() 函数输出调试信息:

log("Debug: variable = " + JSON.stringify(variable));

错误处理

始终检查 API 返回的错误:

var result = fs.readFile(path);
if (result.error) {
    log("Error reading file: " + result.error);
    return;
}

配置测试

在 Web 控制台的插件管理页面安装并配置插件,或通过 API 安装:

# 安装 JS 插件到客户端
POST /api/client/{id}/plugin/js/install
Content-Type: application/json
{
  "plugin_name": "my-plugin",
  "source": "function metadata() {...}",
  "rule_name": "my-rule",
  "remote_port": 8080,
  "config": {"debug": "true"},
  "auto_start": true
}

常见问题

Q: 插件无法加载?

A: 检查签名文件是否存在且有效。

Q: 文件操作失败?

A: 确认路径在沙箱允许范围内。

Q: 如何获取客户端 IP

A: 目前 API 不支持,计划在后续版本添加。


更新日志

v1.0.0

  • 初始版本
  • 支持基础 API: log, config
  • 支持连接 API: Read, Write, Close
  • 支持文件系统 API: fs.*
  • 支持 HTTP API: http.*