feat: remove unused plugin version comparison and types, refactor proxy server to support authentication

- Deleted version comparison logic from `pkg/plugin/sign/version.go`.
- Removed unused types and constants from `pkg/plugin/types.go`.
- Updated `pkg/protocol/message.go` to remove plugin-related message types.
- Enhanced `pkg/proxy/http.go` and `pkg/proxy/socks5.go` to include username/password authentication for HTTP and SOCKS5 proxies.
- Modified `pkg/proxy/server.go` to pass authentication parameters to server constructors.
- Added new API endpoint to generate installation commands with a token for clients.
- Created database functions to manage installation tokens in `internal/server/db/install_token.go`.
- Implemented the installation command generation logic in `internal/server/router/handler/install.go`.
- Updated web frontend to support installation command generation and display in `web/src/views/ClientsView.vue`.
This commit is contained in:
2026-03-17 23:16:30 +08:00
parent dcfd2f4466
commit 5a03d9e1f1
42 changed files with 638 additions and 6161 deletions

View File

@@ -0,0 +1,45 @@
package db
// CreateInstallToken 创建安装token
func (s *SQLiteStore) CreateInstallToken(token *InstallToken) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := s.db.Exec(`INSERT INTO install_tokens (token, client_id, created_at, used) VALUES (?, ?, ?, ?)`,
token.Token, token.ClientID, token.CreatedAt, 0)
return err
}
// GetInstallToken 获取安装token
func (s *SQLiteStore) GetInstallToken(token string) (*InstallToken, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var t InstallToken
var used int
err := s.db.QueryRow(`SELECT token, client_id, created_at, used FROM install_tokens WHERE token = ?`, token).
Scan(&t.Token, &t.ClientID, &t.CreatedAt, &used)
if err != nil {
return nil, err
}
t.Used = used == 1
return &t, nil
}
// MarkTokenUsed 标记token已使用
func (s *SQLiteStore) MarkTokenUsed(token string) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := s.db.Exec(`UPDATE install_tokens SET used = 1 WHERE token = ?`, token)
return err
}
// DeleteExpiredTokens 删除过期token
func (s *SQLiteStore) DeleteExpiredTokens(expireTime int64) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := s.db.Exec(`DELETE FROM install_tokens WHERE created_at < ?`, expireTime)
return err
}

View File

@@ -2,52 +2,11 @@ package db
import "github.com/gotunnel/pkg/protocol"
// ConfigField 配置字段定义
type ConfigField struct {
Key string `json:"key"`
Label string `json:"label"`
Type string `json:"type"`
Default string `json:"default,omitempty"`
Required bool `json:"required,omitempty"`
Options []string `json:"options,omitempty"`
Description string `json:"description,omitempty"`
}
// ClientPlugin 客户端已安装的插件
type ClientPlugin struct {
ID string `json:"id"` // 插件实例唯一 ID
Name string `json:"name"`
Version string `json:"version"`
Enabled bool `json:"enabled"`
Running bool `json:"running"` // 运行状态
Config map[string]string `json:"config,omitempty"` // 插件配置
RemotePort int `json:"remote_port,omitempty"` // 远程监听端口
ConfigSchema []ConfigField `json:"config_schema,omitempty"` // 配置模式
AuthEnabled bool `json:"auth_enabled,omitempty"` // 是否启用认证
AuthUsername string `json:"auth_username,omitempty"` // 认证用户名
AuthPassword string `json:"auth_password,omitempty"` // 认证密码
}
// Client 客户端数据
type Client struct {
ID string `json:"id"`
Nickname string `json:"nickname,omitempty"`
Rules []protocol.ProxyRule `json:"rules"`
Plugins []ClientPlugin `json:"plugins,omitempty"` // 已安装的插件
}
// JSPlugin JS 插件数据
type JSPlugin struct {
Name string `json:"name"`
Source string `json:"source"`
Signature string `json:"signature"` // 官方签名 (Base64)
Description string `json:"description"`
Author string `json:"author"`
Version string `json:"version,omitempty"`
AutoPush []string `json:"auto_push"`
Config map[string]string `json:"config"`
AutoStart bool `json:"auto_start"`
Enabled bool `json:"enabled"`
}
// ClientStore 客户端存储接口
@@ -62,20 +21,9 @@ type ClientStore interface {
Close() error
}
// JSPluginStore JS 插件存储接口
type JSPluginStore interface {
GetAllJSPlugins() ([]JSPlugin, error)
GetJSPlugin(name string) (*JSPlugin, error)
SaveJSPlugin(p *JSPlugin) error
DeleteJSPlugin(name string) error
SetJSPluginEnabled(name string, enabled bool) error
UpdateJSPluginConfig(name string, config map[string]string) error
}
// Store 统一存储接口
type Store interface {
ClientStore
JSPluginStore
TrafficStore
Close() error
}
@@ -94,3 +42,19 @@ type TrafficStore interface {
Get24HourTraffic() (inbound, outbound int64, err error)
GetHourlyTraffic(hours int) ([]TrafficRecord, error)
}
// InstallToken 安装token
type InstallToken struct {
Token string `json:"token"`
ClientID string `json:"client_id"`
CreatedAt int64 `json:"created_at"`
Used bool `json:"used"`
}
// InstallTokenStore 安装token存储接口
type InstallTokenStore interface {
CreateInstallToken(token *InstallToken) error
GetInstallToken(token string) (*InstallToken, error)
MarkTokenUsed(token string) error
DeleteExpiredTokens(expireTime int64) error
}

View File

@@ -40,8 +40,7 @@ func (s *SQLiteStore) init() error {
CREATE TABLE IF NOT EXISTS clients (
id TEXT PRIMARY KEY,
nickname TEXT NOT NULL DEFAULT '',
rules TEXT NOT NULL DEFAULT '[]',
plugins TEXT NOT NULL DEFAULT '[]'
rules TEXT NOT NULL DEFAULT '[]'
)
`)
if err != nil {
@@ -50,36 +49,6 @@ func (s *SQLiteStore) init() error {
// 迁移:添加 nickname 列
s.db.Exec(`ALTER TABLE clients ADD COLUMN nickname TEXT NOT NULL DEFAULT ''`)
// 迁移:添加 plugins 列
s.db.Exec(`ALTER TABLE clients ADD COLUMN plugins TEXT NOT NULL DEFAULT '[]'`)
// 创建 JS 插件表
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS js_plugins (
name TEXT PRIMARY KEY,
source TEXT NOT NULL,
signature TEXT NOT NULL DEFAULT '',
description TEXT,
author TEXT,
version TEXT DEFAULT '',
auto_push TEXT NOT NULL DEFAULT '[]',
config TEXT NOT NULL DEFAULT '{}',
auto_start INTEGER DEFAULT 1,
enabled INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
if err != nil {
return err
}
// 迁移:添加 signature 列
s.db.Exec(`ALTER TABLE js_plugins ADD COLUMN signature TEXT NOT NULL DEFAULT ''`)
// 迁移:添加 version 列
s.db.Exec(`ALTER TABLE js_plugins ADD COLUMN version TEXT DEFAULT ''`)
// 迁移:添加 updated_at 列
s.db.Exec(`ALTER TABLE js_plugins ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP`)
// 创建流量统计表
_, err = s.db.Exec(`
@@ -108,6 +77,19 @@ func (s *SQLiteStore) init() error {
// 初始化总流量记录
s.db.Exec(`INSERT OR IGNORE INTO traffic_total (id, inbound, outbound) VALUES (1, 0, 0)`)
// 创建安装token表
_, err = s.db.Exec(`
CREATE TABLE IF NOT EXISTS install_tokens (
token TEXT PRIMARY KEY,
client_id TEXT NOT NULL,
created_at INTEGER NOT NULL,
used INTEGER NOT NULL DEFAULT 0
)
`)
if err != nil {
return err
}
return nil
}
@@ -121,7 +103,7 @@ func (s *SQLiteStore) GetAllClients() ([]Client, error) {
s.mu.RLock()
defer s.mu.RUnlock()
rows, err := s.db.Query(`SELECT id, nickname, rules, plugins FROM clients`)
rows, err := s.db.Query(`SELECT id, nickname, rules FROM clients`)
if err != nil {
return nil, err
}
@@ -130,16 +112,13 @@ func (s *SQLiteStore) GetAllClients() ([]Client, error) {
var clients []Client
for rows.Next() {
var c Client
var rulesJSON, pluginsJSON string
if err := rows.Scan(&c.ID, &c.Nickname, &rulesJSON, &pluginsJSON); err != nil {
var rulesJSON string
if err := rows.Scan(&c.ID, &c.Nickname, &rulesJSON); err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(rulesJSON), &c.Rules); err != nil {
c.Rules = []protocol.ProxyRule{}
}
if err := json.Unmarshal([]byte(pluginsJSON), &c.Plugins); err != nil {
c.Plugins = []ClientPlugin{}
}
clients = append(clients, c)
}
return clients, nil
@@ -151,17 +130,14 @@ func (s *SQLiteStore) GetClient(id string) (*Client, error) {
defer s.mu.RUnlock()
var c Client
var rulesJSON, pluginsJSON string
err := s.db.QueryRow(`SELECT id, nickname, rules, plugins FROM clients WHERE id = ?`, id).Scan(&c.ID, &c.Nickname, &rulesJSON, &pluginsJSON)
var rulesJSON string
err := s.db.QueryRow(`SELECT id, nickname, rules FROM clients WHERE id = ?`, id).Scan(&c.ID, &c.Nickname, &rulesJSON)
if err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(rulesJSON), &c.Rules); err != nil {
c.Rules = []protocol.ProxyRule{}
}
if err := json.Unmarshal([]byte(pluginsJSON), &c.Plugins); err != nil {
c.Plugins = []ClientPlugin{}
}
return &c, nil
}
@@ -174,12 +150,8 @@ func (s *SQLiteStore) CreateClient(c *Client) error {
if err != nil {
return err
}
pluginsJSON, err := json.Marshal(c.Plugins)
if err != nil {
return err
}
_, err = s.db.Exec(`INSERT INTO clients (id, nickname, rules, plugins) VALUES (?, ?, ?, ?)`,
c.ID, c.Nickname, string(rulesJSON), string(pluginsJSON))
_, err = s.db.Exec(`INSERT INTO clients (id, nickname, rules) VALUES (?, ?, ?)`,
c.ID, c.Nickname, string(rulesJSON))
return err
}
@@ -192,12 +164,8 @@ func (s *SQLiteStore) UpdateClient(c *Client) error {
if err != nil {
return err
}
pluginsJSON, err := json.Marshal(c.Plugins)
if err != nil {
return err
}
_, err = s.db.Exec(`UPDATE clients SET nickname = ?, rules = ?, plugins = ? WHERE id = ?`,
c.Nickname, string(rulesJSON), string(pluginsJSON), c.ID)
_, err = s.db.Exec(`UPDATE clients SET nickname = ?, rules = ? WHERE id = ?`,
c.Nickname, string(rulesJSON), c.ID)
return err
}
@@ -229,121 +197,6 @@ func (s *SQLiteStore) GetClientRules(id string) ([]protocol.ProxyRule, error) {
return c.Rules, nil
}
// ========== JS 插件存储方法 ==========
// GetAllJSPlugins 获取所有 JS 插件
func (s *SQLiteStore) GetAllJSPlugins() ([]JSPlugin, error) {
s.mu.RLock()
defer s.mu.RUnlock()
rows, err := s.db.Query(`
SELECT name, source, signature, description, author, version, auto_push, config, auto_start, enabled
FROM js_plugins
`)
if err != nil {
return nil, err
}
defer rows.Close()
var plugins []JSPlugin
for rows.Next() {
var p JSPlugin
var autoPushJSON, configJSON string
var version sql.NullString
var autoStart, enabled int
err := rows.Scan(&p.Name, &p.Source, &p.Signature, &p.Description, &p.Author,
&version, &autoPushJSON, &configJSON, &autoStart, &enabled)
if err != nil {
return nil, err
}
p.Version = version.String
json.Unmarshal([]byte(autoPushJSON), &p.AutoPush)
json.Unmarshal([]byte(configJSON), &p.Config)
p.AutoStart = autoStart == 1
p.Enabled = enabled == 1
plugins = append(plugins, p)
}
return plugins, nil
}
// GetJSPlugin 获取单个 JS 插件
func (s *SQLiteStore) GetJSPlugin(name string) (*JSPlugin, error) {
s.mu.RLock()
defer s.mu.RUnlock()
var p JSPlugin
var autoPushJSON, configJSON string
var version sql.NullString
var autoStart, enabled int
err := s.db.QueryRow(`
SELECT name, source, signature, description, author, version, auto_push, config, auto_start, enabled
FROM js_plugins WHERE name = ?
`, name).Scan(&p.Name, &p.Source, &p.Signature, &p.Description, &p.Author,
&version, &autoPushJSON, &configJSON, &autoStart, &enabled)
if err != nil {
return nil, err
}
p.Version = version.String
json.Unmarshal([]byte(autoPushJSON), &p.AutoPush)
json.Unmarshal([]byte(configJSON), &p.Config)
p.AutoStart = autoStart == 1
p.Enabled = enabled == 1
return &p, nil
}
// SaveJSPlugin 保存 JS 插件
func (s *SQLiteStore) SaveJSPlugin(p *JSPlugin) error {
s.mu.Lock()
defer s.mu.Unlock()
autoPushJSON, _ := json.Marshal(p.AutoPush)
configJSON, _ := json.Marshal(p.Config)
autoStart, enabled := 0, 0
if p.AutoStart {
autoStart = 1
}
if p.Enabled {
enabled = 1
}
_, err := s.db.Exec(`
INSERT OR REPLACE INTO js_plugins
(name, source, signature, description, author, version, auto_push, config, auto_start, enabled, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
`, p.Name, p.Source, p.Signature, p.Description, p.Author, p.Version,
string(autoPushJSON), string(configJSON), autoStart, enabled)
return err
}
// DeleteJSPlugin 删除 JS 插件
func (s *SQLiteStore) DeleteJSPlugin(name string) error {
s.mu.Lock()
defer s.mu.Unlock()
_, err := s.db.Exec(`DELETE FROM js_plugins WHERE name = ?`, name)
return err
}
// SetJSPluginEnabled 设置 JS 插件启用状态
func (s *SQLiteStore) SetJSPluginEnabled(name string, enabled bool) error {
s.mu.Lock()
defer s.mu.Unlock()
val := 0
if enabled {
val = 1
}
_, err := s.db.Exec(`UPDATE js_plugins SET enabled = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?`, val, name)
return err
}
// UpdateJSPluginConfig 更新 JS 插件配置
func (s *SQLiteStore) UpdateJSPluginConfig(name string, config map[string]string) error {
s.mu.Lock()
defer s.mu.Unlock()
configJSON, _ := json.Marshal(config)
_, err := s.db.Exec(`UPDATE js_plugins SET config = ?, updated_at = CURRENT_TIMESTAMP WHERE name = ?`, string(configJSON), name)
return err
}
// ========== 流量统计方法 ==========
// getHourTimestamp 获取当前小时的时间戳