package handlers import ( "database/sql" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "strconv" "time" "super-frpc/config" "super-frpc/database" "super-frpc/utils" "super-frpc/postLog" "super-frpc/service" ) func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) { userID, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[CreateInstanceHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to read request body: %v", err)) utils.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("[CreateInstanceHandler] Failed to unmarshal request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } bootAtStart := false if bas, ok := reqMap["bootAtStart"]; ok { switch v := bas.(type) { case bool: bootAtStart = v case string: if v == "true" { bootAtStart = true } } } instanceInfoMap, ok := reqMap["instanceInfo"].(map[string]interface{}) if !ok { utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceInfo format") return } instanceInfo := config.InstanceInfo{ Name: getStringFromMap(instanceInfoMap, "name"), ServerAddr: getStringFromMap(instanceInfoMap, "serverAddr"), ServerPort: getStringFromMap(instanceInfoMap, "serverPort"), AuthMethod: getStringFromMap(instanceInfoMap, "auth_method"), RunUser: getStringFromMap(instanceInfoMap, "runUser"), } if additional, ok := reqMap["additionalProperties"].(map[string]interface{}); ok { instanceInfo.Additional = additional } req := config.CreateInstanceRequest{ InstanceInfo: instanceInfo, BootAtStart: bootAtStart, RunUser: instanceInfo.RunUser, Additional: instanceInfo.Additional, } if req.InstanceInfo.Name == "" || req.InstanceInfo.ServerAddr == "" || req.InstanceInfo.ServerPort == "" || req.InstanceInfo.AuthMethod == "" { utils.SendErrorResponse(w, http.StatusBadRequest, "Missing required fields in instanceInfo") return } runUser := req.RunUser if runUser == "" { runUser = "root" } user, err := database.GetUserByID(userID) if err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to get user info: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info") return } configDir, err := service.GetConfigDir() if err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to get config directory: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get config directory") return } configFileName := fmt.Sprintf("superfrpc_%s_%s.toml", user.Username, req.InstanceInfo.Name) configPath := filepath.Join(configDir, configFileName) setKeyTextWrapper := func(configPath, key, section, value string) error { return config.SetKeyText(configPath, key, section, value, os.ReadFile, os.WriteFile) } if err := config.HandleConfigFileCreate(configPath, req.InstanceInfo, setKeyTextWrapper); err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create config file %s: %v", configPath, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to create config file") return } instance := database.FrpcInstance{ UserID: userID, Name: req.InstanceInfo.Name, BootAtStart: req.BootAtStart, RunUser: runUser, ConfigPath: configPath, Watchdog: 0, } if err := database.DBAddFrpcInstance(instance); err != nil { os.Remove(configPath) postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to save instance %s to database: %v", req.InstanceInfo.Name, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to save instance to database") return } createdInstance, err := database.DBQueryFrpcInstance(userID, req.InstanceInfo.Name) if err != nil { os.Remove(configPath) postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to query created instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query created instance") return } if err := service.CreateBootService(createdInstance.ID); err != nil { database.DBRemoveFrpcInstanceByID(createdInstance.ID) os.Remove(configPath) postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create boot service for instance %s: %v", req.InstanceInfo.Name, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to create boot service") return } if req.BootAtStart { if err := service.SetBootAtStart(createdInstance.ID); err != nil { postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to set boot at start for instance %s: %v", req.InstanceInfo.Name, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to set boot at start") return } } utils.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) { userID, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[DeleteInstanceHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to read request body: %v", err)) utils.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)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] User %d tried to delete a not existed instance: %d", userID, instanceID)) return } if err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if err := service.RemoveBootService(instanceID); err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to remove boot service for instance %s: %v", instance.Name, err)) utils.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)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove config file") return } } if err := database.DBRemoveFrpcInstanceByID(instanceID); err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to delete instance %d from database: %v", instanceID, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to delete instance from database") return } utils.SendSuccessResponse(w, "Instance deleted successfully", map[string]interface{}{ "id": instanceID, }) postLog.Info(fmt.Sprintf("[DeleteInstanceHandler] Instance %d deleted successfully", instanceID)) } func ModifyInstanceHandler(w http.ResponseWriter, r *http.Request) { userID, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[ModifyInstanceHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to read request body: %v", err)) utils.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)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } modifyType := getStringFromMap(reqMap, "type") if modifyType == "" { utils.SendErrorResponse(w, http.StatusBadRequest, "type is required") return } if modifyType != "configFile" && modifyType != "systemConfig" { utils.SendErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("Unknown modify type %s", modifyType)) return } modifiedData, ok := reqMap["modifiedData"].(map[string]interface{}) if !ok || modifiedData == nil { utils.SendErrorResponse(w, http.StatusBadRequest, "modifiedData is required and must be an object") return } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] User %d tried to modify a not existed instance: %d", userID, instanceID)) utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") return } if err != nil { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] User %d does not have permission to modify instance %d", userID, instanceID)) utils.SendErrorResponse(w, http.StatusForbidden, "Permission denied") return } user, err := database.GetUserByID(instance.UserID) if err != nil { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] Failed to get user info: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info") return } if modifyType == "configFile" { handleConfigFileModify(w, instance, modifiedData) } else { handleSystemConfigModify(w, r, instance, modifiedData, user) } } func handleConfigFileModify(w http.ResponseWriter, instance database.FrpcInstance, modifiedData map[string]interface{}) { configPath := instance.ConfigPath for key, value := range modifiedData { configKey := key if key == "auth_method" { configKey = "auth.method" } if key == "auth_token" { configKey = "auth.token" } 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 := config.SetKeyText(configPath, configKey, "", configValue, os.ReadFile, os.WriteFile); err != nil { postLog.Error(fmt.Sprintf("[handleConfigFileModify] Failed to set key %s: %v", key, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to set key %s", key)) return } } utils.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 handleSystemConfigModify(w http.ResponseWriter, r *http.Request, instance database.FrpcInstance, modifiedData map[string]interface{}, user *database.User) { newName := instance.Name newRunUser := instance.RunUser newBootAtStart := 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 newName != instance.Name || newRunUser != instance.RunUser { configDir, err := service.GetConfigDir() if err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to get config directory: %v", err)) utils.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)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to rename config file") return } } } } else { newConfigPath = oldConfigPath } instance.RunUser = newRunUser instance.BootAtStart = newBootAtStart instance.ConfigPath = newConfigPath if err := database.DBUpdateFrpcInstance(instance); err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to update instance in database: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to update instance in database") return } if newBootAtStart { if err := service.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 := service.SetBootAtStart(instance.ID); err != nil { postLog.Error(fmt.Sprintf("[handleSystemConfigModify] Failed to set boot at start: %v", err)) if bootServiceError != "" { bootServiceError += "; " } bootServiceError += fmt.Sprintf("Failed to set boot at start: %v", err) } } else { if err := service.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 } instance.Name = newName data := map[string]interface{}{ "instanceName": newName, "instanceID": instance.ID, "configPath": newConfigPath, "bootAtStart": newBootAtStart, "runUser": newRunUser, } if bootServiceError != "" { data["bootServiceError"] = bootServiceError } utils.SendSuccessResponse(w, "System config modified successfully", data) } func GetUserInstances(userID int) ([]database.FrpcInstance, error) { rows, err := database.DBQueryUserInstances(userID) if err != nil { return nil, err } defer rows.Close() var instances []database.FrpcInstance for rows.Next() { var instance database.FrpcInstance var createdAtStr string if err := rows.Scan( &instance.ID, &instance.UserID, &instance.Name, &instance.BootAtStart, &instance.RunUser, &instance.ConfigPath, &createdAtStr, &instance.Watchdog, ); 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 "" } func getNumFromMap(m map[string]interface{}, key string) int { if v, ok := m[key].(int); ok { return v } if v, ok := m[key].(float64); ok { return int(v) } return 0 } func ListInstancesHandler(w http.ResponseWriter, r *http.Request) { userID, err := utils.Auth(w, r, http.MethodGet) if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[ListInstancesHandler] Auth failed: %v", err)) return } instances, err := GetUserInstances(userID) if err != nil { postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to get user instances: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get instances") return } instanceList := make([]map[string]interface{}, len(instances)) for i, inst := range instances { instanceData := map[string]interface{}{ "instanceID": inst.ID, "name": inst.Name, "createdAt": inst.CreatedAt, "createdBy": inst.CreatedBy, "isRunning": false, } err = service.IsInstanceRunning(inst.ID) if err != nil { instanceData["isRunning"] = false } else { instanceData["isRunning"] = true } instanceList[i] = instanceData } utils.SendSuccessResponse(w, "Instances retrieved successfully", instanceList) postLog.Info(fmt.Sprintf("[ListInstancesHandler] Retrieved %d instances for user %d", len(instances), userID)) } func StartInstanceHandler(w http.ResponseWriter, r *http.Request) { userID, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[StartInstanceHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to read request body: %v", err)) utils.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("[StartInstanceHandler] Failed to unmarshal request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") postLog.Error(fmt.Sprintf("[StartInstanceHandler] User %d tried to start a not existed instance: %d", userID, instanceID)) return } if err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { utils.SendErrorResponse(w, http.StatusForbidden, "Instance not found") postLog.Error(fmt.Sprintf("[StartInstanceHandler] User %d tried to start instance %d that does not belong to them", userID, instanceID)) return } initType := service.GetInitSystem() serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get service name: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name") return } switch initType { case "windows": if err := service.StartWindowsService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start Windows service %s: %v", serviceName, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start Windows service: %v", err)) return } postLog.Debug(fmt.Sprintf("[StartInstanceHandler] Windows service %s started successfully", serviceName)) case "systemd": if err := service.StartSystemdService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start systemd service %s: %v", serviceName, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start systemd service: %v", err)) return } postLog.Debug(fmt.Sprintf("[StartInstanceHandler] Systemd service %s started successfully", serviceName)) case "init.d": if err := service.StartInitDService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start init.d service %s: %v", serviceName, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start init.d service: %v", err)) return } postLog.Debug(fmt.Sprintf("[StartInstanceHandler] Init.d service %s started successfully", serviceName)) default: utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType)) return } utils.SendSuccessResponse(w, "Instance started successfully", map[string]interface{}{ "instanceID": instanceID, "serviceName": serviceName, }) postLog.Info(fmt.Sprintf("[StartInstanceHandler] Instance %d started successfully", instanceID)) } func StopInstanceHandler(w http.ResponseWriter, r *http.Request) { userID, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[StopInstanceHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to read request body: %v", err)) utils.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("[StopInstanceHandler] Failed to unmarshal request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") postLog.Error(fmt.Sprintf("[StopInstanceHandler] User %d tried to stop a not existed instance: %d", userID, instanceID)) return } if err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { utils.SendErrorResponse(w, http.StatusForbidden, "Instance not found") postLog.Error(fmt.Sprintf("[StopInstanceHandler] User %d tried to stop instance %d that does not belong to them", userID, instanceID)) return } initType := service.GetInitSystem() serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to get service name: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name") return } switch initType { case "windows": if err := service.StopWindowsService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to stop Windows service %s: %v", serviceName, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop Windows service: %v", err)) return } postLog.Debug(fmt.Sprintf("[StopInstanceHandler] Windows service %s stopped successfully", serviceName)) case "systemd": if err := service.StopSystemdService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to stop systemd service %s: %v", serviceName, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop systemd service: %v", err)) return } postLog.Debug(fmt.Sprintf("[StopInstanceHandler] Systemd service %s stopped successfully", serviceName)) case "init.d": if err := service.StopInitDService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to stop init.d service %s: %v", serviceName, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop init.d service: %v", err)) return } postLog.Debug(fmt.Sprintf("[StopInstanceHandler] Init.d service %s stopped successfully", serviceName)) default: utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType)) return } utils.SendSuccessResponse(w, "Instance stopped successfully", map[string]interface{}{ "instanceID": instanceID, "serviceName": serviceName, }) postLog.Info(fmt.Sprintf("[StopInstanceHandler] Instance %d stopped successfully", instanceID)) } func RestartInstanceHandler(w http.ResponseWriter, r *http.Request) { userID, err := utils.Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[RestartInstanceHandler] Auth failed: %v", err)) return } body, err := io.ReadAll(r.Body) if err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to read request body: %v", err)) utils.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("[RestartInstanceHandler] Failed to unmarshal request body: %v", err)) utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") postLog.Error(fmt.Sprintf("[RestartInstanceHandler] User %d tried to restart a not existed instance: %d", userID, instanceID)) return } if err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { utils.SendErrorResponse(w, http.StatusForbidden, "Instance not found") postLog.Error(fmt.Sprintf("[RestartInstanceHandler] User %d tried to restart instance %d that does not belong to them", userID, instanceID)) return } initType := service.GetInitSystem() serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get service name: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name") return } switch initType { case "windows": if err := service.RestartWindowsService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart Windows service %s: %v", serviceName, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart Windows service: %v", err)) return } postLog.Debug(fmt.Sprintf("[RestartInstanceHandler] Windows service %s restarted successfully", serviceName)) case "systemd": if err := service.RestartSystemdService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart systemd service %s: %v", serviceName, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart systemd service: %v", err)) return } postLog.Debug(fmt.Sprintf("[RestartInstanceHandler] Systemd service %s restarted successfully", serviceName)) case "init.d": if err := service.RestartInitDService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart init.d service %s: %v", serviceName, err)) utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart init.d service: %v", err)) return } postLog.Debug(fmt.Sprintf("[RestartInstanceHandler] Init.d service %s restarted successfully", serviceName)) default: utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType)) return } utils.SendSuccessResponse(w, "Instance restarted successfully", map[string]interface{}{ "instanceID": instanceID, "serviceName": serviceName, }) postLog.Info(fmt.Sprintf("[RestartInstanceHandler] Instance %d restarted successfully", instanceID)) } func GetInstanceStatusHandler(w http.ResponseWriter, r *http.Request) { userID, err := utils.Auth(w, r, http.MethodGet) if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[GetInstanceStatusHandler] Auth failed: %v", err)) return } queryParams := r.URL.Query() instanceIDStr := queryParams.Get("instanceID") if instanceIDStr == "" { utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") return } if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { utils.SendErrorResponse(w, http.StatusForbidden, "Instance not found") return } err = service.IsInstanceRunning(instanceID) isRunning := err == nil utils.SendSuccessResponse(w, "Instance status retrieved successfully", map[string]interface{}{ "instanceID": instanceID, "isRunning": isRunning, }) } func GetInstanceInfoHandler(w http.ResponseWriter, r *http.Request) { userID, err := utils.Auth(w, r, http.MethodGet) if err != nil { utils.SendErrorResponse(w, http.StatusUnauthorized, err.Error()) postLog.Warning(fmt.Sprintf("[GetInstanceInfoHandler] Auth failed: %v", err)) return } queryParams := r.URL.Query() instanceIDStr := queryParams.Get("instanceID") if instanceIDStr == "" { utils.SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { utils.SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := database.DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { utils.SendErrorResponse(w, http.StatusNotFound, "Instance not found") return } if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to query instance: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { utils.SendErrorResponse(w, http.StatusForbidden, "Instance not found") return } user, err := database.GetUserByID(userID) if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to get user info: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user info") return } serviceName, err := database.GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to get service name: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name") return } configContent, err := os.ReadFile(instance.ConfigPath) if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to read config file: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to read config file") return } frpcConfig, err := config.DecodeFrpcConfig(string(configContent)) if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to decode frpc config: %v", err)) utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to decode frpc config") return } err = service.IsInstanceRunning(instanceID) isRunning := err == nil response := map[string]interface{}{ "name": instance.Name, "serviceName": serviceName, "createdAt": instance.CreatedAt.Format(time.RFC3339), "createdBy": instance.CreatedBy, "isRunning": isRunning, "auth_method": frpcConfig.Global["auth.method"], "bootAtStart": instance.BootAtStart, "runUser": instance.RunUser, "configPath": instance.ConfigPath, } if user.Type == "admin" || user.Type == "superuser" { response["serverAddr"] = frpcConfig.Global["serverAddr"] response["serverPort"] = frpcConfig.Global["serverPort"] } utils.SendSuccessResponse(w, "Instance info retrieved successfully", response) }