Remove sensitive information from instance listing response for all users and add isRunning field Fix backend to prevent starting already running frpc instances
975 lines
34 KiB
Go
975 lines
34 KiB
Go
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) {
|
|
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()
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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) {
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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) {
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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) {
|
|
if r.Method != http.MethodGet {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Debug(fmt.Sprintf("[ListInstancesHandler] Invalid request method: %s", r.Method))
|
|
return
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to validate request header: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
|
|
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) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Debug(fmt.Sprintf("[StartInstanceHandler] Invalid request method: %s", r.Method))
|
|
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
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to validate request header: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
|
|
return
|
|
}
|
|
|
|
userType, err := GetUserType(userID)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get user type: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type")
|
|
return
|
|
}
|
|
if userType != "admin" && userType != "superuser" {
|
|
SendErrorResponse(w, http.StatusForbidden, "Permission Denied")
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Permission Denied for user %d (type: %s)", userID, userType))
|
|
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) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Debug(fmt.Sprintf("[StopInstanceHandler] Invalid request method: %s", r.Method))
|
|
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
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to validate request header: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
|
|
return
|
|
}
|
|
|
|
userType, err := GetUserType(userID)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Failed to get user type: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type")
|
|
return
|
|
}
|
|
if userType != "admin" && userType != "superuser" {
|
|
SendErrorResponse(w, http.StatusForbidden, "Permission Denied")
|
|
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Permission Denied for user %d (type: %s)", userID, userType))
|
|
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) {
|
|
if r.Method != http.MethodPost {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Debug(fmt.Sprintf("[RestartInstanceHandler] Invalid request method: %s", r.Method))
|
|
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
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to validate request header: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
|
|
return
|
|
}
|
|
|
|
userType, err := GetUserType(userID)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get user type: %v", err))
|
|
SendErrorResponse(w, http.StatusInternalServerError, "Failed to get user type")
|
|
return
|
|
}
|
|
if userType != "admin" && userType != "superuser" {
|
|
SendErrorResponse(w, http.StatusForbidden, "Permission Denied")
|
|
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Permission Denied for user %d (type: %s)", userID, userType))
|
|
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) {
|
|
if r.Method != http.MethodGet {
|
|
SendErrorResponse(w, http.StatusMethodNotAllowed, "Invalid request method")
|
|
postLog.Debug(fmt.Sprintf("[GetInstanceStatusHandler] Invalid request method: %s", r.Method))
|
|
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
|
|
}
|
|
|
|
userID, _, err := ValidateRequestWithHeader(w, r)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[GetInstanceStatusHandler] Failed to validate request header: %v", err))
|
|
SendErrorResponse(w, http.StatusBadRequest, "Invalid request header")
|
|
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))
|
|
}
|