- 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
684 lines
23 KiB
Go
684 lines
23 KiB
Go
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))
|
||
}
|
||
sb.WriteString("\n")
|
||
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 ""
|
||
}
|