986 lines
35 KiB
Go
986 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 getIntFromMap(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) {
|
|
_, 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
|
|
}
|
|
|
|
_, 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
|
|
}
|
|
|
|
_, 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
|
|
}
|
|
|
|
_, 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) {
|
|
_, 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
|
|
}
|
|
|
|
_, 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
|
|
}
|
|
|
|
// Check if the instance belongs to the user
|
|
// 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)
|
|
}
|