Initial commit: plugin repository structure
Some checks failed
Sign Plugins / sign (push) Failing after 32s
Some checks failed
Sign Plugins / sign (push) Failing after 32s
- GitHub Actions workflows for signing and validation - Example file-manager plugin - Scripts for batch signing
This commit is contained in:
20
.github/workflows/release.yml
vendored
Normal file
20
.github/workflows/release.yml
vendored
Normal file
@@ -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
|
||||||
43
.github/workflows/sign.yml
vendored
Normal file
43
.github/workflows/sign.yml
vendored
Normal file
@@ -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
|
||||||
16
.github/workflows/validate.yml
vendored
Normal file
16
.github/workflows/validate.yml
vendored
Normal file
@@ -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
|
||||||
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Signatures (auto-generated)
|
||||||
|
*.sig
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
25
README.md
Normal file
25
README.md
Normal file
@@ -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 官方私钥签名,客户端内置公钥验证。
|
||||||
8
plugins/file-manager/manifest.json
Normal file
8
plugins/file-manager/manifest.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "file-manager",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "文件管理器插件,提供远程文件浏览和管理功能",
|
||||||
|
"author": "GoTunnel Official",
|
||||||
|
"run_at": "client",
|
||||||
|
"type": "app"
|
||||||
|
}
|
||||||
64
plugins/file-manager/plugin.js
Normal file
64
plugins/file-manager/plugin.js
Normal file
@@ -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 }) };
|
||||||
|
}
|
||||||
43
scripts/sign-all.sh
Executable file
43
scripts/sign-all.sh
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
KEY_FILE="$1"
|
||||||
|
|
||||||
|
if [ -z "$KEY_FILE" ]; then
|
||||||
|
echo "Usage: $0 <private-key-file>"
|
||||||
|
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!"
|
||||||
29
scripts/validate.sh
Executable file
29
scripts/validate.sh
Executable file
@@ -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
|
||||||
Reference in New Issue
Block a user