package main import ( "database/sql" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "strings" "super-frpc/postLog" "time" "gopkg.in/ini.v1" ) type InstanceInfo struct { Name string `json:"name"` ServerAddr string `json:"serverAddr"` ServerPort string `json:"serverPort"` AuthMethod string `json:"auth_method"` // BootAtStart bool `json:"bootAtStart"` 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"` } var frpcDB *sql.DB func CloseFrpcDatabase() error { if frpcDB != nil { return frpcDB.Close() } return nil } func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method") postLog.Debug(fmt.Sprintf("[CreateInstanceHandler] Invalid request method: %s", r.Method)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to read request body: %v", err)) SendErrorResponse(w, http.StatusBadRequest, "Failed to read request body") return } defer r.Body.Close() // 先解析为map,处理类型不匹配的情况 var reqMap map[string]interface{} if err := json.Unmarshal(body, &reqMap); err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to unmarshal request body: %v", err)) SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } // 处理bootAtStart字段 bootAtStart := false if bas, ok := reqMap["bootAtStart"]; ok { switch v := bas.(type) { case bool: bootAtStart = v case string: if v == "true" { bootAtStart = true } } } // 处理instanceInfo字段 instanceInfoMap, ok := reqMap["instanceInfo"].(map[string]interface{}) if !ok { SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceInfo format") return } instanceInfo := InstanceInfo{ Name: getStringFromMap(instanceInfoMap, "name"), ServerAddr: getStringFromMap(instanceInfoMap, "serverAddr"), ServerPort: getStringFromMap(instanceInfoMap, "serverPort"), AuthMethod: getStringFromMap(instanceInfoMap, "auth_method"), RunUser: getStringFromMap(reqMap, "runUser"), } // 处理additionalProperties字段 if additional, ok := reqMap["additionalProperties"].(map[string]interface{}); ok { instanceInfo.Additional = additional } // 从Header中验证token和timeStamp userID, _, err := ValidateRequestWithHeader(w, r) if err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to validate request header: %v", err)) SendErrorResponse(w, http.StatusUnauthorized, err.Error()) return } // 构建请求结构体 req := CreateInstanceRequest{ InstanceInfo: instanceInfo, BootAtStart: bootAtStart, RunUser: instanceInfo.RunUser, Additional: instanceInfo.Additional, } if err := CheckPermission(userID, "superuser", "admin"); err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to check permission: %v", err)) SendErrorResponse(w, http.StatusForbidden, err.Error()) return } if req.InstanceInfo.Name == "" || req.InstanceInfo.ServerAddr == "" || req.InstanceInfo.ServerPort == "" || req.InstanceInfo.AuthMethod == "" { SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in instanceInfo") return } runUser := req.RunUser if runUser == "" { runUser = "root" } user, err := GetUserByID(userID) if err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to get user info: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info") return } configDir, err := GetConfigDir() if err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to get config directory: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get config directory") return } // Add frpc instance configFileName := fmt.Sprintf("superfrpc_%s_%s.toml", user.Username, req.InstanceInfo.Name) configPath := filepath.Join(configDir, configFileName) configContent := generateFrpcConfig(req.InstanceInfo) if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create config file %s: %v", configPath, err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to create config file") return } 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, } if err := DBAddFrpcInstance(instance); err != nil { os.Remove(configPath) postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to save instance %s to database: %v", req.InstanceInfo.Name, err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to save instance to database") return } if req.BootAtStart { 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 } } // Finish add frpc instance SendSuccessResponse(w, "Instance created successfully", map[string]interface{}{ "name": req.InstanceInfo.Name, "configPath": configPath, "bootAtStart": req.BootAtStart, }) postLog.Info(fmt.Sprintf("[CreateInstanceHandler] Instance %s created successfully: configPath=%s, bootAtStart=%v, runUser=%s, additionalProperties=%v", req.InstanceInfo.Name, configPath, req.BootAtStart, runUser, req.Additional)) } func DeleteInstanceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method") return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] 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("[DeleteInstanceHandler] Failed to unmarshal request body: %v", err)) SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceName := getStringFromMap(reqMap, "instanceName") if instanceName == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceName is required") return } userID, _, err := ValidateRequestWithHeader(w, r) if err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to validate request header: %v", err)) SendErrorResponse(w, http.StatusUnauthorized, err.Error()) return } if err := CheckPermission(userID, "superuser", "admin"); err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to check permission: %v", err)) SendErrorResponse(w, http.StatusForbidden, err.Error()) 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 // err = frpcDB.QueryRow(` // SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt // FROM frpcInstances WHERE userID = ? AND name = ? // `, userID, instanceName).Scan( // &instance.ID, &instance.UserID, &instance.Name, &instance.ServerAddr, &instance.ServerPort, // &instance.AuthMethod, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &instance.CreatedAt) instance, err = DBQueryFrpcInstance(userID, instanceName) 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: %s", userID, instanceName)) return } if err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to query instance: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.BootAtStart { if err := removeBootService(user.Username, instanceName); err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to remove boot service for instance %s: %v", instanceName, err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove boot service") return } } if _, err := os.Stat(instance.ConfigPath); err == nil { if err := os.Remove(instance.ConfigPath); err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to remove config file %s: %v", instance.ConfigPath, err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove config file") return } } if err := DBRemoveFrpcInstance(userID, instanceName); err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to delete instance %s from database: %v", instanceName, err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to delete instance from database") return } SendSuccessResponse(w, "Instance deleted successfully", map[string]interface{}{ "name": instanceName, }) postLog.Info(fmt.Sprintf("[DeleteInstanceHandler] Instance %s deleted successfully", instanceName)) } func ModifyInstanceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Invalid request method: %s", r.Method)) SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method") return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] 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("[ModifyInstanceHandler] Failed to unmarshal request body: %v", err)) SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceName := getStringFromMap(reqMap, "instanceName") if instanceName == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceName is required") return } instanceID := getStringFromMap(reqMap, "instanceID") if instanceID == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } modifyType := getStringFromMap(reqMap, "type") if modifyType == "" { SendErrorResponse(w, http.StatusBadRequest, "type is required") return } if modifyType != "configFile" && modifyType != "systemConfig" { // Detect valid modify type SendErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("Unknown modify type %s", modifyType)) return } modifiedData, ok := reqMap["modifiedData"].(map[string]interface{}) if !ok || modifiedData == nil { SendErrorResponse(w, http.StatusBadRequest, "modifiedData is required and must be an object") return } // 从Header中验证token和timeStamp userID, _, err := ValidateRequestWithHeader(w, r) if err != nil { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to validate request header: %v", err)) SendErrorResponse(w, http.StatusUnauthorized, err.Error()) return } if err := CheckPermission(userID, "superuser", "admin"); err != nil { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to check permission: %v", err)) SendErrorResponse(w, http.StatusForbidden, err.Error()) return } user, err := GetUserByID(userID) if err != nil { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to get user info: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info") return } instance, err := DBQueryFrpcInstance(userID, instanceName) if err == sql.ErrNoRows { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] User %d tried to modify a not existed instance: %s", userID, instanceName)) SendErrorResponse(w, http.StatusNotFound, "Instance not found") return } if err != nil { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to query instance: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } // 验证 instanceID 是否匹配 if fmt.Sprintf("%d", instance.ID) != instanceID { SendErrorResponse(w, http.StatusBadRequest, "instanceID does not match instanceName") return } if modifyType == "configFile" { handleConfigFileModify(w, instance, modifiedData, user.Username) } else { handleSystemConfigModify(w, r, instance, modifiedData, user) } } func handleConfigFileModify(w http.ResponseWriter, instance FrpcInstance, modifiedData map[string]interface{}, username string) { configPath := instance.ConfigPath // Read current config file content 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 } // Parse config file content 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 } // Write updated config file content back to file 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 } // Update instance fields in database 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 } SendSuccessResponse(w, "Config file modified successfully", map[string]interface{}{ "instanceName": instance.Name, "instanceID": instance.ID, "configPath": configPath, }) postLog.Info(fmt.Sprintf("[handleConfigFileModify] Config file for instance %s modified successfully", instance.Name)) } func updateCommonSection(configContent string, modifiedData map[string]interface{}) (string, error) { cfg, err := ini.Load([]byte(configContent)) if err != nil { return "", fmt.Errorf("failed to parse config: %w", err) } commonSection := cfg.Section("common") if commonSection == nil { return "", fmt.Errorf("common section not found") } for key, value := range modifiedData { commonSection.Key(key).SetValue(formatConfigValue(value)) } var buf strings.Builder if _, err := cfg.WriteTo(&buf); err != nil { return "", fmt.Errorf("failed to write config: %w", err) } return buf.String(), nil } func formatConfigValue(value interface{}) string { switch v := value.(type) { case string: return v case bool: return fmt.Sprintf("%t", v) case float64: if v == float64(int64(v)) { return fmt.Sprintf("%d", int64(v)) } return fmt.Sprintf("%f", v) default: return fmt.Sprintf("%v", v) } } func handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance FrpcInstance, modifiedData map[string]interface{}, user *User) { newName := instance.Name newRunUser := instance.RunUser newBootAtStart := instance.BootAtStart oldBootAtStart := instance.BootAtStart var bootServiceError string if v, ok := modifiedData["name"].(string); ok && v != "" { newName = v } if v, ok := modifiedData["runUser"].(string); ok { newRunUser = v } if v, ok := modifiedData["bootAtStart"].(bool); ok { newBootAtStart = v } oldConfigPath := instance.ConfigPath var newConfigPath string // If instance name or run user changed, need to rename config file if newName != instance.Name || newRunUser != instance.RunUser { configDir, err := GetConfigDir() if err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to get config directory: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get config directory") return } newConfigFileName := fmt.Sprintf("superfrpc_%s_%s.toml", user.Username, newName) newConfigPath = filepath.Join(configDir, newConfigFileName) if oldConfigPath != newConfigPath { if _, err := os.Stat(oldConfigPath); err == nil { if err := os.Rename(oldConfigPath, newConfigPath); err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to rename config file %s to %s: %v", oldConfigPath, newConfigPath, err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to rename config file") return } } } } else { newConfigPath = oldConfigPath } // Update instance fields in database instance.Name = newName instance.RunUser = newRunUser instance.BootAtStart = newBootAtStart instance.ConfigPath = newConfigPath if err := DBUpdateFrpcInstance(instance); err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to update instance in database: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to update instance in database") return } // Handle boot service creation and removal if oldBootAtStart && !newBootAtStart { if err := removeBootService(user.Username, instance.Name); err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to remove boot service: %v", err)) bootServiceError = fmt.Sprintf("Failed to remove boot service: %v", err) } } else if !oldBootAtStart && newBootAtStart { if err := createBootService(user.Username, newName, newConfigPath, newRunUser); err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to create boot service: %v", err)) bootServiceError = fmt.Sprintf("Failed to create boot service: %v", err) } } else if oldBootAtStart && newBootAtStart && (instance.Name != newName || instance.RunUser != newRunUser) { if err := removeBootService(user.Username, instance.Name); err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to remove old boot service: %v", err)) bootServiceError = fmt.Sprintf("Failed to remove old boot service: %v", err) } if err := createBootService(user.Username, newName, newConfigPath, newRunUser); err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to create new boot service: %v", err)) if bootServiceError != "" { bootServiceError += "; " } bootServiceError += fmt.Sprintf("Failed to create new boot service: %v", err) } } data := map[string]interface{}{ "instanceName": newName, "instanceID": instance.ID, "configPath": newConfigPath, "bootAtStart": newBootAtStart, "runUser": newRunUser, } if bootServiceError != "" { data["bootServiceError"] = bootServiceError } SendSuccessResponse(w, "System config modified successfully", data) postLog.Info(fmt.Sprintf("[handleSystemConfigModify] System config for instance %s modified successfully: bootAtStart=%v, runUser=%s", newName, newBootAtStart, newRunUser)) } func ListInstancesHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method") return } userID, _, err := ValidateRequestWithHeader(w, r) if err != nil { postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to validate request: %v", err)) SendErrorResponse(w, http.StatusUnauthorized, "Failed to validate request") return } userType, err := GetUserType(userID) if err != nil { postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to get user type: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type") return } instanceList, err := DBListFrpcInstances() if err != nil { postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to query instances: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instances") return } var responseInstances []map[string]interface{} for _, instance := range instanceList { instanceData := map[string]interface{}{ "instanceID": instance.ID, "name": instance.Name, "serverAddr": instance.ServerAddr, "serverPort": instance.ServerPort, "auth_method": instance.AuthMethod, "bootAtStart": instance.BootAtStart, "runUser": instance.RunUser, "configPath": instance.ConfigPath, "createdAt": instance.CreatedAt, "createdBy": instance.CreatedBy, } if userType == "visitor" { delete(instanceData, "serverAddr") delete(instanceData, "serverPort") delete(instanceData, "auth_method") } responseInstances = append(responseInstances, instanceData) } if responseInstances == nil { responseInstances = []map[string]interface{}{} } SendSuccessResponse(w, "Instances retrieved successfully", responseInstances) } func generateFrpcConfig(info InstanceInfo) string { var sb strings.Builder sb.WriteString("[common]\n") sb.WriteString(fmt.Sprintf("server_addr = %s\n", info.ServerAddr)) sb.WriteString(fmt.Sprintf("server_port = %s\n", info.ServerPort)) sb.WriteString(fmt.Sprintf("auth_method = %s\n", info.AuthMethod)) for key, value := range info.Additional { sb.WriteString(fmt.Sprintf("%s = %v\n", key, value)) } 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() } func GetUserInstances(userID int) ([]FrpcInstance, error) { rows, err := frpcDB.Query(` SELECT id, userID, name, serverAddr, serverPort, auth_method, bootAtStart, runUser, configPath, createdAt FROM frpcInstances WHERE userID = ? `, userID) if err != nil { return nil, err } defer rows.Close() var instances []FrpcInstance for rows.Next() { 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, ); err != nil { return nil, err } instance.CreatedAt, _ = time.Parse(time.RFC3339, createdAtStr) instances = append(instances, instance) } return instances, nil } func getStringFromMap(m map[string]interface{}, key string) string { if v, ok := m[key].(string); ok { return v } return "" }