feat(proxy): add proxy management endpoint and proxy creation API
- Implement new endpoint for creating frpc proxy configurations - Add DBQueryFrpcInstanceByID function to fetch instances by ID - Move proxy generation logic to separate function in frpcProxyAct.go - Update API documentation with new proxy creation endpoint
This commit is contained in:
BIN
Binary file not shown.
+12
@@ -361,6 +361,18 @@ func DBAddFrpcInstance(instance FrpcInstance) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DBQueryFrpcInstanceByID(instanceID int) (FrpcInstance, error) {
|
||||||
|
var instance FrpcInstance
|
||||||
|
var createdAtStr string
|
||||||
|
err := frpcDB.QueryRow("SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt FROM frpcInstances WHERE id = ?", instanceID).Scan(
|
||||||
|
&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort, &instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr)
|
||||||
|
if err != nil {
|
||||||
|
return instance, fmt.Errorf("failed to query frpc instance: %w", err)
|
||||||
|
}
|
||||||
|
instance.CreatedAt, _ = time.Parse(time.RFC3339, createdAtStr)
|
||||||
|
return instance, nil
|
||||||
|
}
|
||||||
|
|
||||||
func DBQueryFrpcInstance(userID int, instanceName string) (FrpcInstance, error) {
|
func DBQueryFrpcInstance(userID int, instanceName string) (FrpcInstance, error) {
|
||||||
var instance FrpcInstance
|
var instance FrpcInstance
|
||||||
var createdAtStr string
|
var createdAtStr string
|
||||||
|
|||||||
+86
@@ -635,6 +635,92 @@ Modify system-level settings such as instance name, boot-at-start configuration,
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Create Proxy
|
||||||
|
|
||||||
|
**Endpoint:** `/frpcAct/proxyMgr/create`
|
||||||
|
**Method:** POST
|
||||||
|
**Content-Type:** application/json
|
||||||
|
**Auth Required:** Yes (token)
|
||||||
|
**Permission Level:** Admin
|
||||||
|
|
||||||
|
Create a new proxy configuration for an existing frpc instance. The proxy configuration will be appended to the instance's config file.
|
||||||
|
|
||||||
|
**Request Headers:**
|
||||||
|
```
|
||||||
|
X-Token: your_token
|
||||||
|
X-Timestamp: 1704067200000
|
||||||
|
```
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"instanceID": "1",
|
||||||
|
"proxyInfo": {
|
||||||
|
"name": "ssh_proxy",
|
||||||
|
"type": "tcp",
|
||||||
|
"localIP": "127.0.0.1",
|
||||||
|
"localPort": "22",
|
||||||
|
"remotePort": "6000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Header | Type | Required | Description |
|
||||||
|
|--------|------|----------|-------------|
|
||||||
|
| X-Token | string | Yes | Authentication token |
|
||||||
|
| X-Timestamp | int64 | Yes | Client timestamp in milliseconds |
|
||||||
|
|
||||||
|
| Field | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| instanceID | string | Yes | Instance ID (the ID of the frpc instance) |
|
||||||
|
| proxyInfo.name | string | Yes | Proxy name |
|
||||||
|
| proxyInfo.type | string | Yes | Proxy type (e.g., tcp, udp, http, https) |
|
||||||
|
| proxyInfo.localIP | string | Yes | Local IP address to forward to |
|
||||||
|
| proxyInfo.localPort | string | Yes | Local port to forward from |
|
||||||
|
| proxyInfo.remotePort | string | Yes | Remote port on frps to expose |
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Proxy created successfully",
|
||||||
|
"data": {
|
||||||
|
"instanceName": "my_frpc",
|
||||||
|
"instanceID": 1,
|
||||||
|
"configPath": "./configs/superfrpc_user_my_frpc.toml",
|
||||||
|
"proxyName": "ssh_proxy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| instanceName | string | Name of the frpc instance |
|
||||||
|
| instanceID | int | Instance ID |
|
||||||
|
| configPath | string | Path to the configuration file |
|
||||||
|
| proxyName | string | Name of the created proxy |
|
||||||
|
|
||||||
|
**Config File Format:**
|
||||||
|
|
||||||
|
The proxy configuration will be appended to the instance's config file in the following format:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[proxies]]
|
||||||
|
name = ssh_proxy
|
||||||
|
type = tcp
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
remote_port = 6000
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:**
|
||||||
|
> - The proxy configuration is appended to the existing config file
|
||||||
|
> - The instance must already exist before creating a proxy
|
||||||
|
> - This endpoint does not modify the database, only the config file
|
||||||
|
> - The frpc service needs to be restarted for changes to take effect
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## List frpc Instances
|
## List frpc Instances
|
||||||
|
|
||||||
**Endpoint:** `/frpcAct/instanceMgr/list`
|
**Endpoint:** `/frpcAct/instanceMgr/list`
|
||||||
|
|||||||
@@ -641,23 +641,10 @@ func generateFrpcConfig(info InstanceInfo) string {
|
|||||||
sb.WriteString(fmt.Sprintf("server_addr = %s\n", info.ServerAddr))
|
sb.WriteString(fmt.Sprintf("server_addr = %s\n", info.ServerAddr))
|
||||||
sb.WriteString(fmt.Sprintf("server_port = %s\n", info.ServerPort))
|
sb.WriteString(fmt.Sprintf("server_port = %s\n", info.ServerPort))
|
||||||
sb.WriteString(fmt.Sprintf("auth_method = %s\n", info.AuthMethod))
|
sb.WriteString(fmt.Sprintf("auth_method = %s\n", info.AuthMethod))
|
||||||
|
|
||||||
for key, value := range info.Additional {
|
for key, value := range info.Additional {
|
||||||
sb.WriteString(fmt.Sprintf("%s = %v\n", key, value))
|
sb.WriteString(fmt.Sprintf("%s = %v\n", key, value))
|
||||||
}
|
}
|
||||||
|
sb.WriteString("\n")
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func addFrpcProxy(info FrpcProxyInfo) string {
|
|
||||||
var sb strings.Builder
|
|
||||||
sb.WriteString("[[proxies]]\n")
|
|
||||||
sb.WriteString(fmt.Sprintf("name = %s\n", info.Name))
|
|
||||||
sb.WriteString(fmt.Sprintf("type = %s\n", info.Type))
|
|
||||||
sb.WriteString(fmt.Sprintf("local_ip = %s\n", info.LocalIP))
|
|
||||||
sb.WriteString(fmt.Sprintf("local_port = %s\n", info.LocalPort))
|
|
||||||
sb.WriteString(fmt.Sprintf("remote_port = %s\n", info.RemotePort))
|
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"super-frpc/postLog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateProxyRequest struct {
|
||||||
|
InstanceID string `json:"instanceID"`
|
||||||
|
ProxyInfo FrpcProxyInfo `json:"proxyInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
||||||
|
postLog.Debug(fmt.Sprintf("[CreateProxyHandler] Invalid request method: %s", r.Method))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to read request body: %v", err))
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
var reqMap map[string]interface{}
|
||||||
|
if err := json.Unmarshal(body, &reqMap); err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to unmarshal request body: %v", err))
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceID := getStringFromMap(reqMap, "instanceID")
|
||||||
|
if instanceID == "" {
|
||||||
|
postLog.Error("[CreateProxyHandler] instanceID is required")
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "instanceID is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyInfoMap, ok := reqMap["proxyInfo"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
postLog.Error("[CreateProxyHandler] Invalid proxyInfo format")
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid proxyInfo format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyInfo := FrpcProxyInfo{
|
||||||
|
Name: getStringFromMap(proxyInfoMap, "name"),
|
||||||
|
Type: getStringFromMap(proxyInfoMap, "type"),
|
||||||
|
LocalIP: getStringFromMap(proxyInfoMap, "localIP"),
|
||||||
|
LocalPort: getStringFromMap(proxyInfoMap, "localPort"),
|
||||||
|
RemotePort: getStringFromMap(proxyInfoMap, "remotePort"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if proxyInfo.Name == "" || proxyInfo.Type == "" || proxyInfo.LocalIP == "" ||
|
||||||
|
proxyInfo.LocalPort == "" || proxyInfo.RemotePort == "" {
|
||||||
|
postLog.Error("[CreateProxyHandler] Missing required fields in proxyInfo")
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in proxyInfo")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to validate request header: %v", err))
|
||||||
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userType, err := GetUserType(userID)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to get user type: %v", err))
|
||||||
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type")
|
||||||
|
return
|
||||||
|
} else if userType != "admin" && userType != "superuser" {
|
||||||
|
SendErrorResponse(w, http.StatusForbidden, "Permission Denied")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var instance FrpcInstance
|
||||||
|
instanceIDInt, _ := strconv.Atoi(instanceID)
|
||||||
|
instance, err = DBQueryFrpcInstanceByID(instanceIDInt)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to query instance: %v", err))
|
||||||
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if instance.UserID != userID {
|
||||||
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Instance not found for user %d", userID))
|
||||||
|
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configContent, err := os.ReadFile(instance.ConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to read config file %s: %v", instance.ConfigPath, err))
|
||||||
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyConfig := addFrpcProxy(proxyInfo)
|
||||||
|
updatedContent := string(configContent) + proxyConfig
|
||||||
|
|
||||||
|
if err := os.WriteFile(instance.ConfigPath, []byte(updatedContent), 0644); err != nil {
|
||||||
|
postLog.Error(fmt.Sprintf("[CreateProxyHandler] Failed to write config file %s: %v", instance.ConfigPath, err))
|
||||||
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SendSuccessResponse(w, "Proxy created successfully", map[string]interface{}{
|
||||||
|
"instanceName": instance.Name,
|
||||||
|
"instanceID": instance.ID,
|
||||||
|
"configPath": instance.ConfigPath,
|
||||||
|
"proxyName": proxyInfo.Name,
|
||||||
|
})
|
||||||
|
postLog.Info(fmt.Sprintf("[CreateProxyHandler] Proxy %s created successfully for instance %s", proxyInfo.Name, instance.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFrpcProxy(info FrpcProxyInfo) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("\n[[proxies]]\n")
|
||||||
|
sb.WriteString(fmt.Sprintf("name = %s\n", info.Name))
|
||||||
|
sb.WriteString(fmt.Sprintf("type = %s\n", info.Type))
|
||||||
|
sb.WriteString(fmt.Sprintf("local_ip = %s\n", info.LocalIP))
|
||||||
|
sb.WriteString(fmt.Sprintf("local_port = %s\n", info.LocalPort))
|
||||||
|
sb.WriteString(fmt.Sprintf("remote_port = %s\n", info.RemotePort))
|
||||||
|
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
@@ -29,4 +29,5 @@ func setupRoutes() {
|
|||||||
http.HandleFunc("/frpcAct/instanceMgr/delete", DeleteInstanceHandler)
|
http.HandleFunc("/frpcAct/instanceMgr/delete", DeleteInstanceHandler)
|
||||||
http.HandleFunc("/frpcAct/instanceMgr/modify", ModifyInstanceHandler)
|
http.HandleFunc("/frpcAct/instanceMgr/modify", ModifyInstanceHandler)
|
||||||
http.HandleFunc("/frpcAct/instanceMgr/list", ListInstancesHandler)
|
http.HandleFunc("/frpcAct/instanceMgr/list", ListInstancesHandler)
|
||||||
|
http.HandleFunc("/frpcAct/proxyMgr/create", CreateProxyHandler)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user