The ownership validation is now handled at an earlier stage in the request processing, making these checks redundant. This change simplifies the code by removing duplicate validation logic.
985 lines
35 KiB
Go
985 lines
35 KiB
Go
package handlers
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"time"
|
|
|
|
"super-frpc/config"
|
|
"super-frpc/database"
|
|
"super-frpc/global"
|
|
"super-frpc/postLog"
|
|
"super-frpc/sys"
|
|
"super-frpc/utils"
|
|
"super-frpc/watchdog"
|
|
)
|
|
|
|
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"
|
|
}
|
|
|
|
configDir, err := sys.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
|
|
}
|
|
|
|
instance := database.FrpcInstance{
|
|
UserID: userID,
|
|
Name: req.InstanceInfo.Name,
|
|
BootAtStart: req.BootAtStart,
|
|
RunUser: runUser,
|
|
ConfigPath: "",
|
|
Watchdog: 0,
|
|
}
|
|
|
|
if err := database.DBAddFrpcInstance(instance); err != nil {
|
|
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 {
|
|
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to query created instance: %v", err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to query created instance")
|
|
return
|
|
}
|
|
|
|
configFileName := fmt.Sprintf("superfrpc_%d.toml", createdInstance.ID)
|
|
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 {
|
|
database.DBRemoveFrpcInstanceByID(createdInstance.ID)
|
|
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
|
|
}
|
|
|
|
createdInstance.ConfigPath = configPath
|
|
if err := database.DBUpdateFrpcInstance(createdInstance); err != nil {
|
|
os.Remove(configPath)
|
|
database.DBRemoveFrpcInstanceByID(createdInstance.ID)
|
|
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to update instance config path: %v", err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to update instance config path")
|
|
return
|
|
}
|
|
|
|
if err := sys.CreateBootService(createdInstance.ID); err != nil {
|
|
database.DBRemoveFrpcInstanceByID(createdInstance.ID)
|
|
os.Remove(configPath)
|
|
postLog.Error(fmt.Sprintf("[CreateInstanceHandler] Failed to create boot sys for instance %s: %v", req.InstanceInfo.Name, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to create boot sys")
|
|
return
|
|
}
|
|
|
|
if req.BootAtStart {
|
|
if err := sys.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 := sys.RemoveBootService(instanceID); err != nil {
|
|
postLog.Error(fmt.Sprintf("[DeleteInstanceHandler] Failed to remove boot sys for instance %s: %v", instance.Name, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to remove boot sys")
|
|
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
|
|
}
|
|
|
|
if modifyType == "configFile" {
|
|
handleConfigFileModify(w, instance, modifiedData)
|
|
} else {
|
|
handleSystemConfigModify(w, r, instance, modifiedData)
|
|
}
|
|
}
|
|
|
|
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{}) {
|
|
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
|
|
}
|
|
|
|
instance.RunUser = newRunUser
|
|
instance.BootAtStart = newBootAtStart
|
|
instance.Name = newName
|
|
|
|
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 := sys.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)
|
|
}
|
|
if err := sys.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 := sys.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)
|
|
}
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"instanceName": newName,
|
|
"instanceID": instance.ID,
|
|
"configPath": instance.ConfigPath,
|
|
"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, "visitor", "admin", "superuser")
|
|
if err != nil {
|
|
utils.SendErrorResponse(w, http.StatusUnauthorized, "invalid token or timestamp")
|
|
postLog.Warning(fmt.Sprintf("[ListInstancesHandler] Auth failed: %v", err))
|
|
return
|
|
}
|
|
|
|
instances, err := database.DBListFrpcInstances()
|
|
postLog.Debug(fmt.Sprintf("[ListInstancesHandler] Retrieved %d instances", len(instances)))
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[ListInstancesHandler] Failed to get all 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 = sys.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 := sys.GetInitSystem()
|
|
sysName, err := database.GetServiceNameByInstanceID(instanceID)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to get sys name: %v", err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get sys name")
|
|
return
|
|
}
|
|
|
|
switch initType {
|
|
case "windows":
|
|
if err := sys.StartWindowsService(sysName); err != nil {
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start Windows sys %s: %v", sysName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start Windows sys: %v", err))
|
|
return
|
|
}
|
|
postLog.Debug(fmt.Sprintf("[StartInstanceHandler] Windows sys %s started successfully", sysName))
|
|
time.Sleep(time.Millisecond * 200)
|
|
if err := sys.IsInstanceRunning(instanceID); err != nil {
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Windows sys %s failed to start: %v", sysName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start Windows sys: %v", err))
|
|
return
|
|
}
|
|
|
|
case "systemd":
|
|
if err := sys.StartSystemdService(sysName); err != nil {
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start systemd sys %s: %v", sysName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start systemd sys: %v", err))
|
|
return
|
|
}
|
|
postLog.Debug(fmt.Sprintf("[StartInstanceHandler] Systemd sys %s started successfully", sysName))
|
|
time.Sleep(time.Millisecond * 200)
|
|
if err := sys.IsInstanceRunning(instanceID); err != nil {
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Systemd sys %s failed to start: %v", sysName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start systemd sys: %v", err))
|
|
return
|
|
}
|
|
|
|
case "init.d":
|
|
if err := sys.StartInitDService(sysName); err != nil {
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Failed to start init.d sys %s: %v", sysName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start init.d sys: %v", err))
|
|
return
|
|
}
|
|
postLog.Debug(fmt.Sprintf("[StartInstanceHandler] Init.d sys %s started successfully", sysName))
|
|
time.Sleep(time.Millisecond * 200)
|
|
if err := sys.IsInstanceRunning(instanceID); err != nil {
|
|
postLog.Error(fmt.Sprintf("[StartInstanceHandler] Init.d sys %s failed to start: %v", sysName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to start init.d sys: %v", err))
|
|
return
|
|
}
|
|
|
|
default:
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Unsupported init system: %s", initType))
|
|
return
|
|
}
|
|
|
|
if global.Is.WatchdogConnected {
|
|
if !watchdog.AddInstance(sysName) {
|
|
postLog.Warning(fmt.Sprintf("[StartInstanceHandler] Failed to add watchdog instance %s", sysName))
|
|
utils.SendSuccessResponse(w, "Instance started successfully but watchdog instance add failed", map[string]interface{}{
|
|
"instanceID": instanceID,
|
|
"sysName": sysName,
|
|
})
|
|
return
|
|
} else {
|
|
utils.SendSuccessResponse(w, "Instance started successfully", map[string]interface{}{
|
|
"instanceID": instanceID,
|
|
"sysName": sysName,
|
|
})
|
|
}
|
|
}
|
|
|
|
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 := sys.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 := sys.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
|
|
}
|
|
time.Sleep(time.Millisecond * 200)
|
|
if err := sys.IsInstanceRunning(instanceID); err == nil {
|
|
postLog.Error(fmt.Sprintf("[StopInstanceHandler] Windows service %s failed to stop: %v", serviceName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to stop Windows sys: %v", err))
|
|
return
|
|
}
|
|
postLog.Debug(fmt.Sprintf("[StopInstanceHandler] Windows service %s stopped successfully", serviceName))
|
|
|
|
case "systemd":
|
|
if err := sys.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 := sys.StopInitDService(serviceName); err != nil {
|
|
time.Sleep(time.Millisecond * 200)
|
|
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
|
|
}
|
|
|
|
if global.Is.WatchdogConnected {
|
|
if !watchdog.RemoveInstance(serviceName) {
|
|
postLog.Warning(fmt.Sprintf("[StopInstanceHandler] Failed to remove watchdog instance %s", serviceName))
|
|
utils.SendSuccessResponse(w, "Instance stopped successfully but watchdog instance remove failed", map[string]interface{}{
|
|
"instanceID": instanceID,
|
|
"serviceName": serviceName,
|
|
})
|
|
} else {
|
|
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 := sys.GetInitSystem()
|
|
sysName, err := database.GetServiceNameByInstanceID(instanceID)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to get sys name: %v", err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get sys name")
|
|
return
|
|
}
|
|
|
|
switch initType {
|
|
case "windows":
|
|
if err := sys.RestartWindowsService(sysName); err != nil {
|
|
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to restart Windows sys %s: %v", sysName, err))
|
|
time.Sleep(time.Millisecond * 200)
|
|
if err := sys.IsInstanceRunning(instanceID); err != nil {
|
|
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Windows sys %s restarted but is not running: %v", sysName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Windows sys restarted but is not running: %v", err))
|
|
return
|
|
}
|
|
postLog.Debug(fmt.Sprintf("[RestartInstanceHandler] Windows sys %s restarted successfully", sysName))
|
|
}
|
|
|
|
case "systemd":
|
|
if err := sys.RestartSystemdService(sysName); err != nil {
|
|
time.Sleep(time.Millisecond * 200)
|
|
if err := sys.IsInstanceRunning(instanceID); err != nil {
|
|
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to check if systemd sys %s is running after restart: %v", sysName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to check if systemd sys is running after restart: %v", err))
|
|
return
|
|
}
|
|
postLog.Debug(fmt.Sprintf("[RestartInstanceHandler] Systemd sys %s restarted successfully", sysName))
|
|
}
|
|
|
|
case "init.d":
|
|
if err := sys.RestartInitDService(sysName); err != nil {
|
|
time.Sleep(time.Millisecond * 200)
|
|
if err := sys.IsInstanceRunning(instanceID); err != nil {
|
|
postLog.Error(fmt.Sprintf("[RestartInstanceHandler] Failed to check init.d sys %s status after restart: %v", sysName, err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf("Failed to check init.d sys status after restart: %v", err))
|
|
return
|
|
}
|
|
postLog.Debug(fmt.Sprintf("[RestartInstanceHandler] Init.d sys %s restarted successfully", sysName))
|
|
}
|
|
|
|
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,
|
|
"sysName": sysName,
|
|
})
|
|
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 = sys.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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
sysName, err := database.GetServiceNameByInstanceID(instanceID)
|
|
if err != nil {
|
|
postLog.Error(fmt.Sprintf("[GetInstanceInfoHandler] Failed to get sys name: %v", err))
|
|
utils.SendErrorResponse(w, http.StatusInternalServerError, "Failed to get sys 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 = sys.IsInstanceRunning(instanceID)
|
|
isRunning := err == nil
|
|
|
|
response := map[string]interface{}{
|
|
"name": instance.Name,
|
|
"sysName": sysName,
|
|
"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)
|
|
}
|