refactor(frpc): restructure instance management and config handling

- Move instance-related structs and functions to config.go
- Remove serverAddr, serverPort, and authMethod from database schema
- Implement new config parsing and encoding with nested key support
- Update service management to use instanceID instead of username/name
- Add GetServiceNameByInstanceID helper function
- Update API documentation for auth.method field change
This commit is contained in:
2026-03-25 20:00:34 +08:00
parent da729b44ff
commit 92a0e24db7
8 changed files with 491 additions and 281 deletions
+84 -162
View File
@@ -9,43 +9,10 @@ import (
"os"
"path/filepath"
"strconv"
"strings"
"super-frpc/postLog"
"time"
"github.com/BurntSushi/toml"
)
type InstanceInfo struct {
Name string `json:"name"`
ServerAddr string `json:"serverAddr"`
ServerPort string `json:"serverPort"`
AuthMethod string `json:"auth_method"`
RunUser string `json:"runUser"`
Additional map[string]interface{} `json:"additionalProperties"`
}
type FrpcProxyInfo struct {
Name string `json:"name"`
Type string `json:"type"`
LocalIP string `json:"local_ip"`
LocalPort string `json:"local_port"`
RemotePort string `json:"remote_port"`
}
type CreateInstanceRequest struct {
InstanceInfo InstanceInfo `json:"instanceInfo"`
BootAtStart bool `json:"bootAtStart"`
RunUser string `json:"runUser"`
Additional map[string]interface{} `json:"additionalProperties"`
}
type FrpcConfig struct {
Common map[string]interface{} `toml:"common"`
Proxies []map[string]interface{} `toml:"proxies"`
Additional map[string]interface{} `toml:"-"`
}
var frpcDB *sql.DB
func CloseFrpcDatabase() error {
@@ -165,9 +132,6 @@ func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) {
instance := FrpcInstance{
UserID: userID,
Name: req.InstanceInfo.Name,
ServerAddr: req.InstanceInfo.ServerAddr,
ServerPort: req.InstanceInfo.ServerPort,
AuthMethod: req.InstanceInfo.AuthMethod,
BootAtStart: req.BootAtStart,
RunUser: runUser,
ConfigPath: configPath,
@@ -180,16 +144,24 @@ func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := createBootService(user.Username, req.InstanceInfo.Name, configPath, runUser); err != nil {
frpcDB.Exec("DELETE FROM frpcInstances WHERE userID = ? AND name = ?", userID, req.InstanceInfo.Name)
os.Remove(configPath)
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create boot service for instance %s: %v", req.InstanceInfo.Name, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to create boot service")
return
}
createdInstance, err := DBQueryFrpcInstance(userID, req.InstanceInfo.Name)
if err != nil {
os.Remove(configPath)
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to query created instance: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to query created instance")
return
}
if err := createBootService(createdInstance.ID); err != nil {
frpcDB.Exec("DELETE FROM frpcInstances WHERE userID = ? AND name = ?", userID, req.InstanceInfo.Name)
os.Remove(configPath)
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create boot service for instance %s: %v", req.InstanceInfo.Name, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to create boot service")
return
}
if req.BootAtStart {
if err := setBootAtStart(user.Username, req.InstanceInfo.Name); err != nil {
if err := setBootAtStart(createdInstance.ID); err != nil {
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to set boot at start for instance %s: %v", req.InstanceInfo.Name, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to set boot at start")
return
@@ -250,15 +222,7 @@ func DeleteInstanceHandler(w http.ResponseWriter, r *http.Request) {
return
}
user, err := GetUserByID(userID)
if err != nil {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to get user info: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info")
return
}
var instance FrpcInstance
instance, err = DBQueryFrpcInstanceByID(instanceID)
instance, err := DBQueryFrpcInstanceByID(instanceID)
if err == sql.ErrNoRows {
SendErrorResponse(w, http.StatusNotFound, "Instance not found")
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] User %d tried to delete a not existed instance: %d", userID, instanceID))
@@ -270,7 +234,7 @@ func DeleteInstanceHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := removeBootService(user.Username, instance.Name); err != nil {
if err := removeBootService(instanceID); err != nil {
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to remove boot service for instance %s: %v", instance.Name, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove boot service")
return
@@ -395,40 +359,40 @@ func ModifyInstanceHandler(w http.ResponseWriter, r *http.Request) {
func handleConfigFileModify(w http.ResponseWriter, instance FrpcInstance, modifiedData map[string]interface{}, username string) {
configPath := instance.ConfigPath
configContent, err := os.ReadFile(configPath)
if err != nil {
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to read config file %s: %v", configPath, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file")
return
}
for key, value := range modifiedData {
configKey := key
if key == "auth_method" {
configKey = "auth.method"
}
if key == "auth_token" {
configKey = "auth.token"
}
updatedConfig, err := updateCommonSection(string(configContent), modifiedData)
if err != nil {
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to update common section: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to update config file")
return
}
if err := os.WriteFile(configPath, []byte(updatedConfig), 0644); err != nil {
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to write config file %s: %v", configPath, err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to write config file")
return
}
if v, ok := modifiedData["server_addr"].(string); ok && v != "" {
instance.ServerAddr = v
}
if v, ok := modifiedData["server_port"].(string); ok && v != "" {
instance.ServerPort = v
}
if v, ok := modifiedData["auth_method"].(string); ok && v != "" {
instance.AuthMethod = v
}
if err := DBUpdateFrpcInstance(instance); err != nil {
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to update instance in database: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to update instance in database")
return
var configValue string
if key == "serverPort" {
switch v := value.(type) {
case float64:
configValue = strconv.Itoa(int(v))
case int:
configValue = strconv.Itoa(v)
case string:
var intVal int
if _, err := fmt.Sscanf(v, "%d", &intVal); err == nil {
configValue = strconv.Itoa(intVal)
} else {
configValue = v
}
default:
configValue = fmt.Sprintf("%v", value)
}
} else {
configValue = fmt.Sprintf("%v", value)
}
if err := setKeyText(configPath, configKey, "", configValue); err != nil {
postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to set key %s: %v", key, err))
SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to set key %s", key))
return
}
}
SendSuccessResponse(w, "Config file modified successfully", map[string]interface{}{
@@ -439,24 +403,6 @@ func handleConfigFileModify(w http.ResponseWriter, instance FrpcInstance, modifi
postLog.Info(fmt.Sprintf("[handleConfigFileModify] Config file for instance %s modified successfully", instance.Name))
}
func updateCommonSection(configContent string, modifiedData map[string]interface{}) (string, error) {
var config FrpcConfig
if _, err := toml.Decode(configContent, &config); err != nil {
return "", fmt.Errorf("failed to parse config: %w", err)
}
for key, value := range modifiedData {
config.Common[key] = value
}
var buf strings.Builder
if err := toml.NewEncoder(&buf).Encode(config); err != nil {
return "", fmt.Errorf("failed to write config: %w", err)
}
return buf.String(), nil
}
func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance FrpcInstance, modifiedData map[string]interface{}, user *User) {
newName := instance.Name
newRunUser := instance.RunUser
@@ -512,12 +458,12 @@ func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance F
}
if newBootAtStart {
if err := removeBootAtStart(user.Username, instance.Name); err != nil {
if err := removeBootAtStart(instance.ID); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to remove boot at start: %v", err))
bootServiceError = fmt.Sprintf("Failed to remove boot at start: %v", err)
}
instance.Name = newName
if err := setBootAtStart(user.Username, newName); err != nil {
if err := setBootAtStart(instance.ID); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to set boot at start: %v", err))
if bootServiceError != "" {
bootServiceError += "; "
@@ -525,7 +471,7 @@ func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance F
bootServiceError += fmt.Sprintf("Failed to set boot at start: %v", err)
}
} else {
if err := removeBootAtStart(user.Username, instance.Name); err != nil {
if err := removeBootAtStart(instance.ID); err != nil {
postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to remove boot at start: %v", err))
bootServiceError = fmt.Sprintf("Failed to remove boot at start: %v", err)
}
@@ -546,30 +492,9 @@ func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance F
SendSuccessResponse(w, "System config modified successfully", data)
}
func generateFrpcConfig(info InstanceInfo) string {
config := FrpcConfig{
Common: make(map[string]interface{}),
}
config.Common["server_addr"] = info.ServerAddr
config.Common["server_port"] = info.ServerPort
config.Common["auth_method"] = info.AuthMethod
for key, value := range info.Additional {
config.Common[key] = value
}
var buf strings.Builder
if err := toml.NewEncoder(&buf).Encode(config); err != nil {
return ""
}
return buf.String()
}
func GetUserInstances(userID int) ([]FrpcInstance, error) {
rows, err := frpcDB.Query(`
SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt
SELECT id, userID, name, bootAtStart, runUser, configPath, createdAt
FROM frpcInstances WHERE userID = ?
`, userID)
if err != nil {
@@ -582,8 +507,7 @@ func GetUserInstances(userID int) ([]FrpcInstance, error) {
var instance FrpcInstance
var createdAtStr string
if err := rows.Scan(
&instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort,
&instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr,
&instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr,
); err != nil {
return nil, err
}
@@ -642,9 +566,15 @@ func ListInstancesHandler(w http.ResponseWriter, r *http.Request) {
}
if userType == "admin" || userType == "superuser" {
instanceData["serverAddr"] = inst.ServerAddr
instanceData["serverPort"] = inst.ServerPort
instanceData["auth_method"] = inst.AuthMethod
serverAddr, err := getKeyText(inst.ConfigPath, "serverAddr", "")
serverPort, err := getKeyText(inst.ConfigPath, "serverPort", "")
authMethod, err := getKeyText(inst.ConfigPath, "auth.method", "")
if err != nil {
postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to read config for instance %d: %v", inst.ID, err))
}
instanceData["serverAddr"] = serverAddr
instanceData["serverPort"] = serverPort
instanceData["auth_method"] = authMethod
}
instanceList[i] = instanceData
@@ -725,16 +655,14 @@ func StartInstanceHandler(w http.ResponseWriter, r *http.Request) {
return
}
user, err := GetUserByID(instance.UserID)
initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get user info: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info")
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return
}
initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", user.Username, instance.Name)
switch initType {
case "windows":
if err := StartWindowsService(serviceName); err != nil {
@@ -841,16 +769,14 @@ func StopInstanceHandler(w http.ResponseWriter, r *http.Request) {
return
}
user, err := GetUserByID(instance.UserID)
initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to get user info: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info")
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return
}
initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", user.Username, instance.Name)
switch initType {
case "windows":
if err := StopWindowsService(serviceName); err != nil {
@@ -957,16 +883,14 @@ func RestartInstanceHandler(w http.ResponseWriter, r *http.Request) {
return
}
user, err := GetUserByID(instance.UserID)
initType := GetInitSystem()
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get user info: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info")
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return
}
initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", user.Username, instance.Name)
switch initType {
case "windows":
if err := RestartWindowsService(serviceName); err != nil {
@@ -1047,16 +971,14 @@ func GetInstanceStatusHandler(w http.ResponseWriter, r *http.Request) {
return
}
user, err := GetUserByID(instance.UserID)
if err != nil {
postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] Failed to get user info: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info")
return
}
initType := GetInitSystem()
serviceName := fmt.Sprintf("superfrpc_%s_%s", user.Username, instance.Name)
serviceName, err := GetServiceNameByInstanceID(instanceID)
if err != nil {
postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] Failed to get service name: %v", err))
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name")
return
}
responseData := map[string]interface{}{
"name": instance.Name,
@@ -1065,7 +987,7 @@ func GetInstanceStatusHandler(w http.ResponseWriter, r *http.Request) {
"isRunning": false,
}
isRunning := IsInstanceRunning(instance.Name)
isRunning := IsInstanceRunning(instanceID)
responseData["isRunning"] = isRunning
SendSuccessResponse(w, "Instance status retrieved successfully", responseData)