package main import ( "database/sql" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "strconv" "super-frpc/postLog" "time" ) var frpcDB *sql.DB func CloseFrpcDatabase() error { if frpcDB != nil { return frpcDB.Close() } return nil } func CreateInstanceHandler(w http.ResponseWriter, r *http.Request) { userID, err := Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { 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)) 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)) 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 { 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"), } if additional, ok := reqMap["additionalProperties"].(map[string]interface{}); ok { instanceInfo.Additional = additional } req := CreateInstanceRequest{ InstanceInfo: instanceInfo, BootAtStart: bootAtStart, RunUser: instanceInfo.RunUser, Additional: instanceInfo.Additional, } 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 } 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, 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 } 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(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 } } 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 := Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { 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)) 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 } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } 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)) 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 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 } 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 := DBRemoveFrpcInstanceByID(instanceID); err != nil { postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to delete instance %d from database: %v", instanceID, err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to delete instance from database") return } 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 := Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { 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)) 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 } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } modifyType := getStringFromMap(reqMap, "type") if modifyType == "" { SendErrorResponse(w, http.StatusBadRequest, "type is required") return } if modifyType != "configFile" && modifyType != "systemConfig" { 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 } instance, err := DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] User %d tried to modify a not existed instance: %d", userID, instanceID)) 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 } if instance.UserID != userID { postLog.Error(fmt.Sprintf("[ModifyInstanceHandler] User %d does not have permission to modify instance %d", userID, instanceID)) SendErrorResponse(w, http.StatusForbidden, "Permission denied") return } user, err := GetUserByID(instance.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 } 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 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 := 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{}{ "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 FrpcInstance, modifiedData map[string]interface{}, user *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 := 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 } instance.RunUser = newRunUser instance.BootAtStart = newBootAtStart instance.ConfigPath = newConfigPath // Update instance new name will be processed in boot service creation or removal 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 } if newBootAtStart { 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(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 := 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 } SendSuccessResponse(w, "System config modified successfully", data) } func GetUserInstances(userID int) ([]FrpcInstance, error) { rows, err := frpcDB.Query(` SELECT id, userID, name, 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.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 "" } func ListInstancesHandler(w http.ResponseWriter, r *http.Request) { userID, err := Auth(w, r, http.MethodGet) if err != nil { 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)) 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": IsInstanceRunning(inst.ID), } instanceList[i] = instanceData } 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 := Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { 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)) 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)) SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { 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)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { 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 := GetInitSystem() serviceName, err := GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get service name: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name") return } switch initType { case "windows": if err := StartWindowsService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start Windows service %s: %v", serviceName, err)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start Windows service: %v", err)) return } postLog.Info(fmt.Sprintf("[StartInstanceHandler] Windows service %s started successfully", serviceName)) SendSuccessResponse(w, "Instance started successfully", nil) case "systemd": if err := StartSystemdService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start systemd service %s: %v", serviceName, err)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start systemd service: %v", err)) return } postLog.Info(fmt.Sprintf("[StartInstanceHandler] Systemd service %s started successfully", serviceName)) SendSuccessResponse(w, "Instance started successfully", nil) case "init.d": if err := StartInitDService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start init.d service %s: %v", serviceName, err)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start init.d service: %v", err)) return } postLog.Info(fmt.Sprintf("[StartInstanceHandler] Init.d service %s started successfully", serviceName)) SendSuccessResponse(w, "Instance started successfully", nil) default: postLog.Error(fmt.Sprintf("[StartInstanceHandler] Unsupported init system: %s", initType)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType)) return } } func StopInstanceHandler(w http.ResponseWriter, r *http.Request) { userID, err := Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { 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)) 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)) SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { 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)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { 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 := GetInitSystem() serviceName, err := GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to get service name: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name") return } switch initType { case "windows": if err := StopWindowsService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to stop Windows service %s: %v", serviceName, err)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop Windows service: %v", err)) return } postLog.Info(fmt.Sprintf("[StopInstanceHandler] Windows service %s stopped successfully", serviceName)) SendSuccessResponse(w, "Instance stopped successfully", nil) case "systemd": if err := StopSystemdService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to stop systemd service %s: %v", serviceName, err)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop systemd service: %v", err)) return } postLog.Info(fmt.Sprintf("[StopInstanceHandler] Systemd service %s stopped successfully", serviceName)) SendSuccessResponse(w, "Instance stopped successfully", nil) case "init.d": if err := StopInitDService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to stop init.d service %s: %v", serviceName, err)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop init.d service: %v", err)) return } postLog.Info(fmt.Sprintf("[StopInstanceHandler] Init.d service %s stopped successfully", serviceName)) SendSuccessResponse(w, "Instance stopped successfully", nil) default: postLog.Error(fmt.Sprintf("[StopInstanceHandler] Unsupported init system: %s", initType)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType)) return } } func RestartInstanceHandler(w http.ResponseWriter, r *http.Request) { userID, err := Auth(w, r, http.MethodPost, "superuser", "admin") if err != nil { 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)) 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)) SendErrorResponse(w, http.StatusBadRequest, "Invalid request format") return } instanceIDStr := getStringFromMap(reqMap, "instanceID") if instanceIDStr == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { 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)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { 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 := GetInitSystem() serviceName, err := GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get service name: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name") return } switch initType { case "windows": if err := RestartWindowsService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart Windows service %s: %v", serviceName, err)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart Windows service: %v", err)) return } postLog.Info(fmt.Sprintf("[RestartInstanceHandler] Windows service %s restarted successfully", serviceName)) SendSuccessResponse(w, "Instance restarted successfully", nil) case "systemd": if err := RestartSystemdService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart systemd service %s: %v", serviceName, err)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart systemd service: %v", err)) return } postLog.Info(fmt.Sprintf("[RestartInstanceHandler] Systemd service %s restarted successfully", serviceName)) SendSuccessResponse(w, "Instance restarted successfully", nil) case "init.d": if err := RestartInitDService(serviceName); err != nil { postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart init.d service %s: %v", serviceName, err)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to restart init.d service: %v", err)) return } postLog.Info(fmt.Sprintf("[RestartInstanceHandler] Init.d service %s restarted successfully", serviceName)) SendSuccessResponse(w, "Instance restarted successfully", nil) default: postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Unsupported init system: %s", initType)) SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType)) return } } func GetInstanceStatusHandler(w http.ResponseWriter, r *http.Request) { userID, err := Auth(w, r, http.MethodGet) if err != nil { 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 == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { SendErrorResponse(w, http.StatusNotFound, "Instance not found") postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] User %d tried to get status of a not existed instance: %d", userID, instanceID)) return } if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] Failed to query instance: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { SendErrorResponse(w, http.StatusForbidden, "Instance not found") postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] User %d tried to get status of instance %d that does not belong to them", userID, instanceID)) return } initType := GetInitSystem() 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, "initSystem": initType, "serviceName": serviceName, "isRunning": false, } isRunning := IsInstanceRunning(instanceID) responseData["isRunning"] = isRunning SendSuccessResponse(w, "Instance status retrieved successfully", responseData) postLog.Info(fmt.Sprintf("[GetInstanceStatusHandler] Retrieved status for instance %d (name: %s), isRunning: %v", instanceID, instance.Name, isRunning)) } func GetInstanceInfoHandler(w http.ResponseWriter, r *http.Request) { userID, err := Auth(w, r, http.MethodGet) if err != nil { 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 == "" { SendErrorResponse(w, http.StatusBadRequest, "instanceID is required") return } instanceID, err := strconv.Atoi(instanceIDStr) if err != nil { SendErrorResponse(w, http.StatusBadRequest, "Invalid instanceID format") return } instance, err := DBQueryFrpcInstanceByID(instanceID) if err == sql.ErrNoRows { SendErrorResponse(w, http.StatusNotFound, "Instance not found") postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] User %d tried to get info of a not existed instance: %d", userID, instanceID)) return } if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to query instance: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to query instance") return } if instance.UserID != userID { SendErrorResponse(w, http.StatusForbidden, "Instance not found") postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] User %d tried to get info of instance %d that does not belong to them", userID, instanceID)) return } userType, err := GetUserType(userID) if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to get user type: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type") return } serviceName, err := GetServiceNameByInstanceID(instanceID) if err != nil { postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to get service name: %v", err)) SendErrorResponse(w, http.StatusInternalServerError, "Failed to get service name") return } isRunning := IsInstanceRunning(instanceID) responseData := map[string]interface{}{ "name": instance.Name, "serviceName": serviceName, "createdAt": instance.CreatedAt, "createdBy": instance.CreatedBy, "isRunning": isRunning, "auth_method": "", "bootAtStart": instance.BootAtStart, "runUser": instance.RunUser, "configPath": instance.ConfigPath, } configContent, err := os.ReadFile(instance.ConfigPath) if err == nil { config, err := decodeFrpcConfig(string(configContent)) if err == nil { if authMethod, ok := config.Global["auth.method"]; ok { responseData["auth_method"] = authMethod } if userType == "admin" || userType == "superuser" { if serverAddr, ok := config.Global["serverAddr"]; ok { responseData["serverAddr"] = serverAddr } if serverPort, ok := config.Global["serverPort"]; ok { responseData["serverPort"] = serverPort } } } } SendSuccessResponse(w, "Instance info retrieved successfully", responseData) postLog.Info(fmt.Sprintf("[GetInstanceInfoHandler] Retrieved info for instance %d (name: %s), userType: %s", instanceID, instance.Name, userType)) }