feat: implement VPN platform management and real protocol services

This commit is contained in:
Logan.Li committed 2026-07-22 21:49:13 +08:00
commit 58c036c34c
45 files changed
+9244

No files matched your search

+138
View File
@@ -0,0 +1,138 @@
// Package custom 实现自定义协议插件:用户在平台注册命令钩子,
// 平台按统一 Protocol 契约调用(Create/Start/Stop/Status/Config)。
// 钩子通过 stdin 接收专线 Config JSON,约定 stdout 返回结果。
package custom
import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"strings"
"time"
"vpn-platform/backend/internal/core"
"vpn-platform/backend/internal/model"
"vpn-platform/backend/internal/store"
)
// Service 以数据库中的 Plugin 定义为驱动的协议实现
type Service struct {
plugin *model.Plugin
}
func New(p *model.Plugin) *Service { return &Service{plugin: p} }
func (s *Service) Name() string { return "custom:" + s.plugin.Name }
func (s *Service) DisplayName() string { return s.plugin.DisplayName }
func (s *Service) Available() (bool, string) {
if s.plugin.StartHook == "" {
return false, "插件未配置 start 钩子"
}
return true, "插件钩子: " + s.plugin.StartHook
}
// runHook 执行钩子命令,stdin 传入专线上下文 JSON
func (s *Service) runHook(hook string, t *model.Tunnel) (string, error) {
if hook == "" {
return "", fmt.Errorf("插件 [%s] 未配置该动作的钩子命令", s.plugin.Name)
}
payload, _ := json.Marshal(map[string]interface{}{
"id": t.ID,
"name": t.Name,
"protocol": t.Protocol,
"config": json.RawMessage(t.Config),
})
ctx := exec.Command("sh", "-c", hook)
done := make(chan error, 1)
ctx.Stdin = bytes.NewReader(payload)
var out, errOut bytes.Buffer
ctx.Stdout = &out
ctx.Stderr = &errOut
if err := ctx.Start(); err != nil {
return "", err
}
go func() { done <- ctx.Wait() }()
select {
case err := <-done:
if err != nil {
return out.String(), fmt.Errorf("钩子执行失败: %v %s", err, errOut.String())
}
return strings.TrimSpace(out.String()), nil
case <-time.After(30 * time.Second):
_ = ctx.Process.Kill()
return "", fmt.Errorf("钩子执行超时(30s)")
}
}
func (s *Service) Validate(cfg map[string]interface{}) (map[string]interface{}, error) {
out := map[string]interface{}{}
for k, v := range cfg {
out[k] = v
}
return out, nil
}
func (s *Service) Create(t *model.Tunnel) error {
if s.plugin.CreateHook == "" {
return nil // 可选
}
_, err := s.runHook(s.plugin.CreateHook, t)
return err
}
func (s *Service) Start(t *model.Tunnel) error {
_, err := s.runHook(s.plugin.StartHook, t)
return err
}
func (s *Service) Stop(t *model.Tunnel) error {
if s.plugin.StopHook == "" {
return nil
}
_, err := s.runHook(s.plugin.StopHook, t)
return err
}
func (s *Service) Status(t *model.Tunnel) (core.Status, error) {
if s.plugin.StatusHook == "" {
return core.Status{State: "unknown", Detail: "插件未配置 status 钩子", UpdatedAt: timeNow()}, nil
}
out, err := s.runHook(s.plugin.StatusHook, t)
if err != nil {
return core.Status{Running: false, State: "error", Detail: err.Error(), UpdatedAt: timeNow()}, nil
}
// 约定:status 钩子 stdout 输出 running / stopped / 任意描述
running := strings.EqualFold(out, "running") || strings.EqualFold(out, "up") || out == "0"
state := "stopped"
if running {
state = "running"
}
return core.Status{Running: running, State: state, Detail: out, UpdatedAt: timeNow()}, nil
}
func timeNow() int64 { return time.Now().Unix() }
func (s *Service) GenerateClientConfig(t *model.Tunnel) (string, error) {
if s.plugin.ConfigHook == "" {
return "", fmt.Errorf("插件未配置 config 钩子,无法生成客户端配置")
}
return s.runHook(s.plugin.ConfigHook, t)
}
func (s *Service) Delete(t *model.Tunnel) error { return s.Stop(t) }
// LoadAll 从数据库加载全部启用的插件并注册到管理器
func LoadAll(m *core.Manager) {
if store.DB == nil {
return
}
var plugins []model.Plugin
if err := store.DB.Where("enabled = ?", true).Find(&plugins).Error; err != nil {
return
}
for i := range plugins {
m.Register(New(&plugins[i]))
}
}