From 6b38f133f4b883bdb2f1b101466cdfdfe8098f9b Mon Sep 17 00:00:00 2001 From: Flik Date: Mon, 29 Dec 2025 18:53:30 +0800 Subject: [PATCH] Initial commit: plugin repository structure - GitHub Actions workflows for signing and validation - Example file-manager plugin - Scripts for batch signing --- .github/workflows/release.yml | 20 ++++++++++ .github/workflows/sign.yml | 43 ++++++++++++++++++++ .github/workflows/validate.yml | 16 ++++++++ .gitignore | 9 +++++ README.md | 25 ++++++++++++ plugins/file-manager/manifest.json | 8 ++++ plugins/file-manager/plugin.js | 64 ++++++++++++++++++++++++++++++ scripts/sign-all.sh | 43 ++++++++++++++++++++ scripts/validate.sh | 29 ++++++++++++++ 9 files changed, 257 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/sign.yml create mode 100644 .github/workflows/validate.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 plugins/file-manager/manifest.json create mode 100644 plugins/file-manager/plugin.js create mode 100755 scripts/sign-all.sh create mode 100755 scripts/validate.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1856da9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,20 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: | + plugins/**/*.js + plugins/**/*.sig + plugins/**/manifest.json diff --git a/.github/workflows/sign.yml b/.github/workflows/sign.yml new file mode 100644 index 0000000..ec0c9bb --- /dev/null +++ b/.github/workflows/sign.yml @@ -0,0 +1,43 @@ +name: Sign Plugins + +on: + push: + branches: [main] + paths: + - 'plugins/**/*.js' + - 'plugins/**/manifest.json' + workflow_dispatch: + +jobs: + sign: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.21' + + - name: Build signtool + run: | + git clone --depth 1 https://github.com/your-org/gotunnel.git /tmp/gotunnel + cd /tmp/gotunnel + go build -o /usr/local/bin/signtool ./cmd/signtool + + - name: Sign plugins + env: + SIGNING_KEY: ${{ secrets.PLUGIN_SIGNING_KEY }} + run: | + echo "$SIGNING_KEY" > /tmp/private.key + chmod 600 /tmp/private.key + bash scripts/sign-all.sh /tmp/private.key + rm -f /tmp/private.key + + - name: Commit signatures + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + git add -A "plugins/**/*.sig" + git diff --staged --quiet || git commit -m "chore: update plugin signatures" + git push diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..37126a1 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,16 @@ +name: Validate PR + +on: + pull_request: + branches: [main] + paths: + - 'plugins/**' + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate manifests + run: bash scripts/validate.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c9e274 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Signatures (auto-generated) +*.sig + +# OS +.DS_Store + +# IDE +.idea/ +.vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..476288c --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# GoTunnel Official Plugins + +GoTunnel 官方插件仓库,所有插件均经过官方签名验证。 + +## 目录结构 + +``` +plugins/ + ├── file-manager/ # 文件管理器插件 + │ ├── plugin.js # 插件源码 + │ ├── plugin.js.sig # 签名文件(CI自动生成) + │ └── manifest.json # 插件元数据 + └── ... +``` + +## 插件开发 + +1. 在 `plugins/` 下创建插件目录 +2. 编写 `plugin.js` 和 `manifest.json` +3. 提交 PR,CI 会自动验证格式 +4. 合并后 CI 自动签名并发布 + +## 签名验证 + +所有插件由 GoTunnel 官方私钥签名,客户端内置公钥验证。 diff --git a/plugins/file-manager/manifest.json b/plugins/file-manager/manifest.json new file mode 100644 index 0000000..49b0753 --- /dev/null +++ b/plugins/file-manager/manifest.json @@ -0,0 +1,8 @@ +{ + "name": "file-manager", + "version": "1.0.0", + "description": "文件管理器插件,提供远程文件浏览和管理功能", + "author": "GoTunnel Official", + "run_at": "client", + "type": "app" +} diff --git a/plugins/file-manager/plugin.js b/plugins/file-manager/plugin.js new file mode 100644 index 0000000..1400913 --- /dev/null +++ b/plugins/file-manager/plugin.js @@ -0,0 +1,64 @@ +// GoTunnel File Manager Plugin +function metadata() { + return { + name: "file-manager", + version: "1.0.0", + description: "文件管理器插件", + author: "GoTunnel Official", + type: "app", + run_at: "client" + }; +} + +function start() { + log("File Manager plugin started"); +} + +function stop() { + log("File Manager plugin stopped"); +} + +function handleConn(conn) { + http.serve(conn, handleRequest); +} + +function handleRequest(req) { + var path = req.path; + var method = req.method; + + if (path === "/api/list") { + return handleList(req); + } + if (path === "/api/read" && method === "POST") { + return handleRead(req); + } + + return { status: 404, body: '{"error":"not found"}' }; +} + +function handleList(req) { + var dir = config("root_path") || "."; + var result = fs.readDir(dir); + + if (result.error) { + return { status: 500, body: http.json({ error: result.error }) }; + } + + return { status: 200, body: http.json({ entries: result.entries }) }; +} + +function handleRead(req) { + var body = JSON.parse(req.body || "{}"); + var path = body.path; + + if (!path) { + return { status: 400, body: '{"error":"path required"}' }; + } + + var result = fs.readFile(path); + if (result.error) { + return { status: 500, body: http.json({ error: result.error }) }; + } + + return { status: 200, body: http.json({ content: result.data }) }; +} diff --git a/scripts/sign-all.sh b/scripts/sign-all.sh new file mode 100755 index 0000000..73254f4 --- /dev/null +++ b/scripts/sign-all.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +KEY_FILE="$1" + +if [ -z "$KEY_FILE" ]; then + echo "Usage: $0 " + exit 1 +fi + +if [ ! -f "$KEY_FILE" ]; then + echo "Error: Key file not found: $KEY_FILE" + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(dirname "$SCRIPT_DIR")" + +cd "$REPO_ROOT" + +for manifest in plugins/*/manifest.json; do + if [ ! -f "$manifest" ]; then + continue + fi + + dir=$(dirname "$manifest") + name=$(jq -r '.name' "$manifest") + version=$(jq -r '.version' "$manifest") + plugin_file="$dir/plugin.js" + + if [ ! -f "$plugin_file" ]; then + echo "Warning: $plugin_file not found, skipping" + continue + fi + + echo "Signing: $name v$version" + signtool sign -key "$KEY_FILE" \ + -name "$name" \ + -version "$version" \ + "$plugin_file" +done + +echo "Done!" diff --git a/scripts/validate.sh b/scripts/validate.sh new file mode 100755 index 0000000..7344744 --- /dev/null +++ b/scripts/validate.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +ERRORS=0 + +for manifest in plugins/*/manifest.json; do + [ -f "$manifest" ] || continue + dir=$(dirname "$manifest") + name=$(basename "$dir") + + echo "Validating: $name" + + # 检查必需字段 + for field in name version description; do + val=$(jq -r ".$field" "$manifest") + if [ "$val" = "null" ] || [ -z "$val" ]; then + echo " Error: missing $field" + ERRORS=$((ERRORS + 1)) + fi + done + + # 检查 plugin.js 存在 + if [ ! -f "$dir/plugin.js" ]; then + echo " Error: plugin.js not found" + ERRORS=$((ERRORS + 1)) + fi +done + +exit $ERRORS